@awesomate/hosting-mcp 0.87.0 → 0.88.6

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.
Files changed (42) hide show
  1. package/dist/index.js +51 -141
  2. package/package.json +2 -1
  3. package/skill/CHANGELOG.json +43 -0
  4. package/skill/awesomate-app-builder/SKILL.md +13 -8
  5. package/skill/awesomate-app-builder/references/node-recipes.md +3 -2
  6. package/skill/awesomate-app-builder/references/stack-decision.md +4 -3
  7. package/skill/awesomate-bookings/SKILL.md +9 -6
  8. package/skill/awesomate-database/SKILL.md +52 -62
  9. package/skill/awesomate-hosting/SKILL.md +1 -1
  10. package/skill/awesomate-n8n/SKILL.md +165 -108
  11. package/skill/awesomate-n8n/evals/n8n-agent-build/graders/grader.md +2 -1
  12. package/skill/awesomate-n8n/references/ai-agents.md +10 -8
  13. package/skill/awesomate-n8n/references/datatables.md +46 -66
  14. package/skill/awesomate-n8n/references/error-handling.md +5 -5
  15. package/skill/awesomate-n8n/references/n8n-agents.md +10 -9
  16. package/skill/awesomate-n8n/references/onebrain.md +4 -4
  17. package/skill/awesomate-n8n/references/platform-notes.md +13 -13
  18. package/skill/awesomate-n8n/references/possibilities.md +12 -11
  19. package/skill/awesomate-n8n/references/testing-policy.md +51 -47
  20. package/skill/awesomate-n8n/references/troubleshooting.md +15 -16
  21. package/skill/awesomate-n8n/references/webhook-pages.md +5 -5
  22. package/skill/awesomate-n8n/references/wp-form-handler.md +6 -4
  23. package/skill/awesomate-seo/SKILL.md +15 -8
  24. package/skill/awesomate-seo/references/ai-discoverability.md +40 -26
  25. package/skill/awesomate-n8n/evals/ai-agent-build/graders/grader.md +0 -31
  26. package/skill/awesomate-n8n/evals/ai-agent-build/prompt.md +0 -1
  27. package/skill/awesomate-n8n/evals/build-form-email/graders/grader.md +0 -30
  28. package/skill/awesomate-n8n/evals/build-form-email/prompt.md +0 -1
  29. package/skill/awesomate-n8n/evals/datatable-dedupe/graders/grader.md +0 -32
  30. package/skill/awesomate-n8n/evals/datatable-dedupe/prompt.md +0 -2
  31. package/skill/awesomate-n8n/evals/essentials-upsell/graders/grader.md +0 -27
  32. package/skill/awesomate-n8n/evals/essentials-upsell/prompt.md +0 -2
  33. package/skill/awesomate-n8n/evals/live-change-promote/graders/grader.md +0 -35
  34. package/skill/awesomate-n8n/evals/live-change-promote/prompt.md +0 -2
  35. package/skill/awesomate-n8n/evals/validated-not-done/graders/grader.md +0 -27
  36. package/skill/awesomate-n8n/evals/validated-not-done/prompt.md +0 -2
  37. package/skill/awesomate-n8n/evals/webhook-body-fix/graders/grader.md +0 -26
  38. package/skill/awesomate-n8n/evals/webhook-body-fix/prompt.md +0 -3
  39. package/skill/awesomate-n8n/evals/webhook-page-build/graders/grader.md +0 -36
  40. package/skill/awesomate-n8n/evals/webhook-page-build/prompt.md +0 -1
  41. package/skill/awesomate-n8n/references/rest-fallback.md +0 -77
  42. package/skill/awesomate-n8n/references/upgrade-loop.md +0 -98
package/dist/index.js CHANGED
@@ -41747,6 +41747,7 @@ var TITLES = {
41747
41747
  awesomate_elementor_mcp_connect: "Connect Elementor MCP",
41748
41748
  awesomate_app_create: "Create app",
41749
41749
  awesomate_app_get: "Get app details",
41750
+ awesomate_app_remove_failed: "Remove an app whose setup failed",
41750
41751
  awesomate_app_scaffold: "Get app scaffold guide",
41751
41752
  awesomate_app_deploy_info: "Get app deploy briefing",
41752
41753
  awesomate_app_update: "Link repo to app",
@@ -41766,14 +41767,10 @@ var TITLES = {
41766
41767
  awesomate_n8n_findings: "Get n8n findings",
41767
41768
  awesomate_n8n_kpis: "Get n8n KPIs",
41768
41769
  awesomate_n8n_node_docs: "Get n8n node docs",
41769
- awesomate_n8n_deploy: "Deploy n8n workflow",
41770
41770
  awesomate_n8n_agents: "Build n8n Agents",
41771
41771
  awesomate_n8n_library: "Install Awesomate templates",
41772
41772
  awesomate_book_session: "Book a session with Awesomate",
41773
- awesomate_n8n_test: "Test-fire n8n workflow",
41774
- awesomate_n8n_datatable_write: "Write to n8n data table",
41775
41773
  awesomate_n8n_attach_to_app: "Attach n8n to app",
41776
- awesomate_n8n_provision_pg: "Provision Postgres for n8n",
41777
41774
  awesomate_request_build: "Request a build from Awesomate",
41778
41775
  awesomate_knowledge_status: "Get Knowledge Base status",
41779
41776
  awesomate_knowledge_provision: "Enable Knowledge Base",
@@ -41866,65 +41863,9 @@ server.registerTool(
41866
41863
  );
41867
41864
  readTool(
41868
41865
  "awesomate_n8n_context",
41869
- "Call FIRST before any n8n work. Returns the client's n8n instance URL, plan, whether Claude Code n8n access is consented (if consented=false, send the user to settingsUrl and re-check after), builder capability (Support Plus+), instance variant, and build/test quota limits. When consented it also returns an instance fingerprint (community packages, workflow/credential/datatable counts). Companion tools once consented: awesomate_n8n_workflows (world picture), awesomate_n8n_inspect (nodes/datatables/possibilities/credentials/variables), awesomate_n8n_executions, awesomate_n8n_node_docs (live node schemas + community templates \u2014 every plan).",
41866
+ "Call FIRST before any n8n work. Returns the client's n8n instance URL, plan, whether Claude Code n8n access is consented (if consented=false, send the user to settingsUrl and re-check after), template-install capability (Support Plus+) and instance variant. When consented it also returns an instance fingerprint (community packages, workflow/credential/datatable counts). Companion tools once consented: awesomate_n8n_workflows (world picture), awesomate_n8n_inspect (nodes/datatables/possibilities/credentials/variables), awesomate_n8n_executions, awesomate_n8n_node_docs (live node schemas + community templates \u2014 every plan).",
41870
41867
  "/api/my-n8n/machine/context"
41871
41868
  );
41872
- server.registerTool(
41873
- "awesomate_n8n_deploy",
41874
- {
41875
- annotations: annotate("awesomate_n8n_deploy", DESTRUCTIVE),
41876
- description: "Workflow lifecycle writes on the client's n8n, all consent-gated and audited server-side. Actions: 'validate' (structural + n8n-mcp check of workflowJson; 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; one Awesomate built may be switched on or off, and the reply says built_by_us so you can tell the client what it does first, but every other write to it is refused with agency_managed; switching on a workflow installed from the library with an empty or missing credential answers 409 setup_incomplete with the list, so walk the client through filling it first), 'promote' (swaps a TESTED draft into the live workflow IN PLACE: 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; pass operationId), 'update_draft' (replace an INACTIVE self-built draft's full JSON in place; 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: 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.",
41877
- inputSchema: {
41878
- action: external_exports.enum(["validate", "create_draft", "update_draft", "activate", "deactivate", "promote", "rollback", "delete_draft"]),
41879
- workflowJson: external_exports.record(external_exports.unknown()).optional().describe("For validate: the full workflow JSON. For create_draft: must contain name, nodes, connections (settings optional)."),
41880
- workflowId: external_exports.string().optional().describe("Required for activate/deactivate/delete_draft; for promote this is the LIVE workflow id"),
41881
- draftId: external_exports.string().optional().describe("promote only: the tested draft to swap into the live workflow"),
41882
- operationId: external_exports.string().optional().describe("rollback only: the operationId returned by promote")
41883
- }
41884
- },
41885
- async ({ action, workflowJson, workflowId, draftId, operationId }) => {
41886
- try {
41887
- if (action === "validate") {
41888
- return textResult(await hubPost(requireConfig(), "/api/my-n8n/machine/workflows/validate", { workflow: workflowJson }));
41889
- }
41890
- if (action === "create_draft") {
41891
- return textResult(await hubPost(requireConfig(), "/api/my-n8n/machine/workflows/draft", workflowJson ?? {}));
41892
- }
41893
- if (action === "update_draft") {
41894
- if (!workflowId) return errorResult(new Error("workflowId is required for update_draft"));
41895
- if (!workflowJson) return errorResult(new Error("workflowJson (full: name, nodes, connections, settings?) is required for update_draft"));
41896
- return textResult(
41897
- await hubPatch(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}`, workflowJson)
41898
- );
41899
- }
41900
- if (action === "rollback") {
41901
- if (!operationId) return errorResult(new Error("operationId is required for rollback"));
41902
- return textResult(
41903
- await hubPost(requireConfig(), `/api/my-n8n/machine/operations/${encodeURIComponent(operationId)}/rollback`, {})
41904
- );
41905
- }
41906
- if (!workflowId) return errorResult(new Error(`workflowId is required for ${action}`));
41907
- if (action === "activate" || action === "deactivate") {
41908
- return textResult(
41909
- await hubPost(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}/activate`, {
41910
- active: action === "activate"
41911
- })
41912
- );
41913
- }
41914
- if (action === "promote") {
41915
- if (!draftId) return errorResult(new Error("draftId is required for promote"));
41916
- return textResult(
41917
- await hubPost(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}/promote`, { draftId })
41918
- );
41919
- }
41920
- return textResult(
41921
- await hubDelete(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}`)
41922
- );
41923
- } catch (err) {
41924
- return errorResult(err);
41925
- }
41926
- }
41927
- );
41928
41869
  var N8N_AGENT_TOOLS = [
41929
41870
  "get_agent_builder_reference",
41930
41871
  "search_projects",
@@ -41967,7 +41908,7 @@ server.registerTool(
41967
41908
  "awesomate_n8n_agents",
41968
41909
  {
41969
41910
  annotations: annotate("awesomate_n8n_agents", DESTRUCTIVE),
41970
- description: "Build and manage n8n AGENTS (the Agents tab in n8n 2.41+; NOT the AI Agent node inside a workflow, and NOT the older Chat Hub agents) on the client's own n8n. Every action works on EVERY plan, Essentials included. The hub relays one call at a time to n8n's own instance MCP server, consent-gated, quota-limited and audited; the client's MCP key never reaches this machine. Actions: 'status' (is it set up? keyConfigured / reachable / ready / limits; call FIRST, and when not ready relay setup.steps to the user in plain words), 'describe' (n8n's own description + JSON input schema for every relayed tool, or just `tool`; read it before calling a tool whose arguments you have not seen this session, never guess argument shapes), 'call' (run one n8n agent tool: pass `tool` and its `arguments` exactly as its schema defines them), 'result' (call_id: collect a call that was still running). Before the first build in a session call tool 'get_agent_builder_reference' and follow its build sequence and JSON schema; it is n8n's own live guide and beats anything remembered. Flow: search_projects \u2192 discover_agent_assets (models, workflows, subagents) + list_credentials \u2192 create_agent {projectId, name, config} \u2192 mutate_agent {agentId, baseConfigHash, operation} (always the LATEST configHash; result.ok=false with code stale_config \u2192 get_agent and retry) \u2192 validate_agent \u2192 call_agent with one representative test message (real tools run, say so) \u2192 ask the user \u2192 publish_agent ONLY on their explicit yes. call_agent can take a couple of minutes: this tool waits for it. If it comes back pending with a call_id, or 409 agent_call_in_progress, NEVER resend the message; use 'result'. 504 mcp_timeout means it may have run anyway: check before retrying. Workflow tools must start with an Execute Workflow Trigger and be active (Support Plus+ can activate through awesomate_n8n_deploy; on Essentials the client switches it on in n8n). 403 agent_tool_blocked: the config tried to attach a workflow Awesomate manages, or the n8n node / an n8n API credential; use one of the client's own workflows instead. delete_agent only works on agents Claude created. 409 mcp_key_missing / mcp_disabled / mcp_key_rejected = setup problem on THEIR n8n, relay `hint` + `setup`. 429 quota_exceeded = daily plan limit. A response with ok:false is n8n's own answer: read error.code/message and fix the input, don't retry blindly.",
41911
+ description: "Build and manage n8n AGENTS (the Agents tab in n8n 2.41+; NOT the AI Agent node inside a workflow, and NOT the older Chat Hub agents) on the client's own n8n. Every action works on EVERY plan, Essentials included. The hub relays one call at a time to n8n's own instance MCP server, consent-gated, quota-limited and audited; the client's MCP key never reaches this machine. Actions: 'status' (is it set up? keyConfigured / reachable / ready / limits; call FIRST, and when not ready relay setup.steps to the user in plain words), 'describe' (n8n's own description + JSON input schema for every relayed tool, or just `tool`; read it before calling a tool whose arguments you have not seen this session, never guess argument shapes), 'call' (run one n8n agent tool: pass `tool` and its `arguments` exactly as its schema defines them), 'result' (call_id: collect a call that was still running). Before the first build in a session call tool 'get_agent_builder_reference' and follow its build sequence and JSON schema; it is n8n's own live guide and beats anything remembered. Flow: search_projects \u2192 discover_agent_assets (models, workflows, subagents) + list_credentials \u2192 create_agent {projectId, name, config} \u2192 mutate_agent {agentId, baseConfigHash, operation} (always the LATEST configHash; result.ok=false with code stale_config \u2192 get_agent and retry) \u2192 validate_agent \u2192 call_agent with one representative test message (real tools run, say so) \u2192 ask the user \u2192 publish_agent ONLY on their explicit yes. call_agent can take a couple of minutes: this tool waits for it. If it comes back pending with a call_id, or 409 agent_call_in_progress, NEVER resend the message; use 'result'. 504 mcp_timeout means it may have run anyway: check before retrying. Workflow tools must start with an Execute Workflow Trigger and be active (the client switches it on in n8n, or through their own connection to the instance's MCP server). 403 agent_tool_blocked: the config tried to attach a workflow Awesomate manages, or the n8n node / an n8n API credential; use one of the client's own workflows instead. delete_agent only works on agents Claude created. 409 mcp_key_missing / mcp_disabled / mcp_key_rejected = setup problem on THEIR n8n, relay `hint` + `setup`. 429 quota_exceeded = daily plan limit. A response with ok:false is n8n's own answer: read error.code/message and fix the input, don't retry blindly.",
41971
41912
  inputSchema: {
41972
41913
  action: external_exports.enum(["status", "describe", "call", "result"]),
41973
41914
  tool: external_exports.enum(N8N_AGENT_TOOLS).optional().describe("call: the n8n agent tool to run (required). describe: limit to this tool (optional)"),
@@ -42072,26 +42013,6 @@ server.registerTool(
42072
42013
  }
42073
42014
  }
42074
42015
  );
42075
- server.registerTool(
42076
- "awesomate_n8n_test",
42077
- {
42078
- annotations: annotate("awesomate_n8n_test", MUTATING),
42079
- 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.",
42080
- inputSchema: {
42081
- workflowId: external_exports.string(),
42082
- payload: external_exports.record(external_exports.unknown()).optional().describe("JSON body to POST to the webhook (format-valid, obviously-fake values)")
42083
- }
42084
- },
42085
- async ({ workflowId, payload }) => {
42086
- try {
42087
- return textResult(
42088
- await hubPost(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}/test-fire`, { payload })
42089
- );
42090
- } catch (err) {
42091
- return errorResult(err);
42092
- }
42093
- }
42094
- );
42095
42016
  server.registerTool(
42096
42017
  "awesomate_n8n_workflows",
42097
42018
  {
@@ -42128,11 +42049,31 @@ server.registerTool(
42128
42049
  }
42129
42050
  }
42130
42051
  );
42052
+ function withCredentialStaleWarning(data) {
42053
+ if (!data || typeof data !== "object") return data;
42054
+ const d = data;
42055
+ if (d.stale !== true) return data;
42056
+ const asOf = d.asOf ? new Date(d.asOf) : null;
42057
+ const when = asOf && !Number.isNaN(asOf.getTime()) ? `as of ${asOf.toLocaleDateString("en-AU", { timeZone: "Australia/Sydney" })} ${asOf.toLocaleTimeString("en-AU", { timeZone: "Australia/Sydney", hour: "2-digit", minute: "2-digit" })} Sydney time (${d.asOf})` : "with no known refresh time";
42058
+ const why = {
42059
+ consent: `the refresh needs the "How your automations are running" privacy setting${d.settingsUrl ? ` (${d.settingsUrl})` : ""}`,
42060
+ refresh_failed: "the hub could not refresh it from the n8n instance just now",
42061
+ n8n_unreachable: "the hub could not reach the n8n instance's database to check it"
42062
+ }[d.staleReason ?? "refresh_failed"];
42063
+ const parts = [`This credential list may be out of date: it is ${when}, because ${why}.`];
42064
+ const added = d.drift?.added ?? [];
42065
+ const removed = d.drift?.removed ?? [];
42066
+ if (added.length) parts.push(`n8n also has: ${added.map((c) => `${c.name} (${c.type}, ${c.credential_id})`).join(", ")}.`);
42067
+ if (removed.length) parts.push(`No longer in n8n: ${removed.join(", ")}.`);
42068
+ parts.push("Tell the user the list may be out of date before relying on it, and check the credential in n8n itself when it matters.");
42069
+ const { stale: _s, ...rest } = d;
42070
+ return { warning: parts.join(" "), stale: true, ...rest };
42071
+ }
42131
42072
  server.registerTool(
42132
42073
  "awesomate_n8n_inspect",
42133
42074
  {
42134
42075
  annotations: annotate("awesomate_n8n_inspect", READ_ONLY),
42135
- 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.`,
42076
+ 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; a leading 'warning' means the list may be out of date: say so to the user), 'variables' ($vars keys). All consent-gated server-side; 403 consent_required \u2192 settingsUrl.`,
42136
42077
  inputSchema: {
42137
42078
  what: external_exports.enum(["nodes", "datatables", "datatable_rows", "possibilities", "credentials", "variables"]),
42138
42079
  datatableId: external_exports.string().optional().describe("datatable_rows only"),
@@ -42154,7 +42095,8 @@ server.registerTool(
42154
42095
  credentials: "/api/my-n8n/machine/credentials",
42155
42096
  variables: "/api/my-n8n/machine/variables"
42156
42097
  }[what];
42157
- return textResult(await hubGet(cfg, path));
42098
+ const data = await hubGet(cfg, path);
42099
+ return textResult(what === "credentials" ? withCredentialStaleWarning(data) : data);
42158
42100
  } catch (err) {
42159
42101
  return errorResult(err);
42160
42102
  }
@@ -42207,41 +42149,6 @@ server.registerTool(
42207
42149
  }
42208
42150
  }
42209
42151
  );
42210
- server.registerTool(
42211
- "awesomate_n8n_datatable_write",
42212
- {
42213
- annotations: annotate("awesomate_n8n_datatable_write", DESTRUCTIVE),
42214
- 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.",
42215
- inputSchema: {
42216
- action: external_exports.enum(["create", "add_column", "insert", "update", "delete_rows"]),
42217
- tableId: external_exports.string().optional(),
42218
- name: external_exports.string().optional().describe("create: table name; add_column: column name"),
42219
- columns: external_exports.array(external_exports.object({ name: external_exports.string(), type: external_exports.enum(["string", "number", "boolean", "date"]).optional() })).optional(),
42220
- workflowId: external_exports.string().optional().describe("create: co-locate the table with this workflow (recommended)"),
42221
- type: external_exports.enum(["string", "number", "boolean", "date"]).optional().describe("add_column only"),
42222
- rows: external_exports.array(external_exports.record(external_exports.unknown())).optional().describe("insert only"),
42223
- filter: external_exports.record(external_exports.unknown()).optional().describe("update/delete_rows: {filters:[{columnName,condition,value}], type?}"),
42224
- data: external_exports.record(external_exports.unknown()).optional().describe("update only: column \u2192 new value")
42225
- }
42226
- },
42227
- async ({ action, tableId, name, columns, workflowId, type, rows, filter, data }) => {
42228
- try {
42229
- const cfg = requireConfig();
42230
- if (action === "create") {
42231
- return textResult(await hubPost(cfg, "/api/my-n8n/machine/datatables", { name, columns, workflowId }));
42232
- }
42233
- if (!tableId) return errorResult(new Error(`tableId is required for ${action}`));
42234
- const base = `/api/my-n8n/machine/datatables/${encodeURIComponent(tableId)}`;
42235
- if (action === "add_column") {
42236
- return textResult(await hubPost(cfg, `${base}/columns`, { name, type }));
42237
- }
42238
- const mode = action === "delete_rows" ? "delete" : action;
42239
- return textResult(await hubPost(cfg, `${base}/rows`, { mode, rows, filter, data }));
42240
- } catch (err) {
42241
- return errorResult(err);
42242
- }
42243
- }
42244
- );
42245
42152
  server.registerTool(
42246
42153
  "awesomate_skill_update",
42247
42154
  {
@@ -42380,7 +42287,7 @@ server.registerTool(
42380
42287
  "awesomate_domain_add",
42381
42288
  {
42382
42289
  annotations: annotate("awesomate_domain_add", MUTATING),
42383
- 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.",
42290
+ description: "Add a custom domain to the user's hosting account. Returns the DNS steps they need to complete at their registrar: `dnsInstructions` (A rows only when there is a public address; CNAME rows always) and `dnsNote`. When `dnsNote` is set, relay it word for word: on that server the records must be Proxied (orange cloud) in Cloudflare and SSL/TLS set to Full, not Flexible (Full (strict) only once HTTPS is on); a DNS-only record will not load. **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.",
42384
42291
  inputSchema: { domain: external_exports.string().describe("The domain to add, e.g. example.com") }
42385
42292
  },
42386
42293
  async ({ domain }) => {
@@ -42501,7 +42408,7 @@ readTool(
42501
42408
  );
42502
42409
  readTool(
42503
42410
  "awesomate_get_limits",
42504
- "Usage vs plan limits across every dimension (sites, custom domains, apps, database provisions today, workflow credits, AI-editor credits) plus a nudges[] array. Call BEFORE any create action; when a nudge has severity 'approaching' or 'exceeded', surface it to the user with the recommended plan \u2014 never execute an upgrade without preview + explicit confirmation (upgrade tools ship in a later release; for now deep-link to the hub billing page). pgProvisionsToday is a flat daily cap that resets within 24h \u2014 no plan raises it, so don't pitch an upgrade for it.",
42411
+ "Usage vs plan limits across every dimension (sites, custom domains, apps, workflow credits, AI-editor credits) plus a nudges[] array. Call BEFORE any create action; when a nudge has severity 'approaching' or 'exceeded', surface it to the user with the recommended plan \u2014 never execute an upgrade without preview + explicit confirmation (upgrade tools ship in a later release; for now deep-link to the hub billing page).",
42505
42412
  "/api/client-hosting/limits"
42506
42413
  );
42507
42414
  var PLAN_LADDER_FALLBACK = {
@@ -42662,7 +42569,7 @@ server.registerTool(
42662
42569
  "awesomate_app_get",
42663
42570
  {
42664
42571
  annotations: annotate("awesomate_app_get", READ_ONLY),
42665
- description: "Get one app plus its environments (subdomains, ports, db engine/name, deploy + health state) and any hostnames of the user's own attached to them. Poll this after awesomate_app_create \u2014 status goes provisioning \u2192 active | failed (read provision_error on failure).",
42572
+ description: "Get one app plus its environments (subdomains, ports, db engine/name, deploy + health state) and any hostnames of the user's own attached to them. Poll this after awesomate_app_create \u2014 status goes provisioning \u2192 active | failed (read provision_error on failure; to retry, call awesomate_app_create again with the same appSlug, or remove it with awesomate_app_remove_failed).",
42666
42573
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_list / _create") }
42667
42574
  },
42668
42575
  async ({ appId }) => {
@@ -42673,6 +42580,24 @@ server.registerTool(
42673
42580
  }
42674
42581
  }
42675
42582
  );
42583
+ server.registerTool(
42584
+ "awesomate_app_remove_failed",
42585
+ {
42586
+ annotations: annotate("awesomate_app_remove_failed", DESTRUCTIVE),
42587
+ description: "Remove an app whose setup FAILED (awesomate_app_get shows status 'failed'), clearing whatever the attempt left on the user's hosting (subdomains, databases, folders). Only failed apps: an active, provisioning or suspended app is refused with 409 not_failed, and removing a working app stays a support request. A failed app already holds no plan slot and does not reserve its name, so to RETRY just call awesomate_app_create again with the same appSlug: the hub clears the failed attempt first. Use this when the user wants the failed app gone, not to retry. Tell the user what will be removed and get a yes first; confirm must equal the app's slug.",
42588
+ inputSchema: {
42589
+ appId: external_exports.number().int().positive().describe("The failed app id from awesomate_app_list / _get"),
42590
+ confirm: external_exports.string().describe("Must equal the app's slug exactly, as proof of intent")
42591
+ }
42592
+ },
42593
+ async ({ appId, confirm }) => {
42594
+ try {
42595
+ return textResult(await hubDelete(requireConfig(), `/api/my-apps/apps/${appId}`, { confirm }));
42596
+ } catch (err) {
42597
+ return errorResult(err);
42598
+ }
42599
+ }
42600
+ );
42676
42601
  server.registerTool(
42677
42602
  "awesomate_app_create",
42678
42603
  {
@@ -42876,10 +42801,10 @@ server.registerTool(
42876
42801
  "awesomate_n8n_attach_to_app",
42877
42802
  {
42878
42803
  annotations: annotate("awesomate_n8n_attach_to_app", MUTATING),
42879
- 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.",
42804
+ description: "Wire an app to call the user's own n8n as a backend. Give it the app + the production webhook URL of a workflow on their n8n (read it with awesomate_n8n_workflows; a workflow Claude built through the instance's own MCP server works too). 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.",
42880
42805
  inputSchema: {
42881
42806
  appId: external_exports.number().int().positive().describe("The app id"),
42882
- webhookUrl: external_exports.string().url().describe("The n8n workflow's production webhook URL (from awesomate_n8n_deploy)"),
42807
+ webhookUrl: external_exports.string().url().describe("The n8n workflow's production webhook URL"),
42883
42808
  env: external_exports.enum(["dev", "staging", "prod"]).default("dev").describe("Which app environment to wire")
42884
42809
  }
42885
42810
  },
@@ -42927,21 +42852,6 @@ server.registerTool(
42927
42852
  }
42928
42853
  }
42929
42854
  );
42930
- server.registerTool(
42931
- "awesomate_n8n_provision_pg",
42932
- {
42933
- annotations: annotate("awesomate_n8n_provision_pg", MUTATING),
42934
- 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.",
42935
- inputSchema: {}
42936
- },
42937
- async () => {
42938
- try {
42939
- return textResult(await hubPost(requireConfig(), "/api/my-n8n/machine/pg-credentials", {}));
42940
- } catch (err) {
42941
- return errorResult(err);
42942
- }
42943
- }
42944
- );
42945
42855
  function knowledgeResult(data) {
42946
42856
  return {
42947
42857
  content: [
@@ -43731,7 +43641,7 @@ server.registerTool(
43731
43641
  key: external_exports.string().max(41).optional().describe("save_calendar, save_service: lower case, digits and underscores"),
43732
43642
  name: external_exports.string().max(80).optional(),
43733
43643
  timezone: external_exports.string().max(60).optional().describe("IANA zone, like Australia/Sydney"),
43734
- hours: external_exports.record(external_exports.string(), external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()]))).optional().describe('save_calendar: {"mon": [["09:00","17:00"]], ...}'),
43644
+ hours: external_exports.record(external_exports.string(), external_exports.array(external_exports.array(external_exports.string()).length(2))).optional().describe('save_calendar: {"mon": [["09:00","17:00"]], ...}'),
43735
43645
  min_notice_minutes: external_exports.number().int().min(0).optional(),
43736
43646
  buffer_minutes: external_exports.number().int().min(0).optional(),
43737
43647
  max_per_day: external_exports.number().int().min(1).nullable().optional(),
@@ -44432,7 +44342,7 @@ server.registerTool(
44432
44342
  server.registerTool(
44433
44343
  "awesomate_site_audit",
44434
44344
  {
44435
- description: "Run the SEO + AEO build gates against ONE live page the user owns, fetched AS GPTBot. Returns pass/fail per gate with specific fixes: is the content in the raw HTML at all (a React/Vue site is invisible to ChatGPT, Claude and Perplexity \u2014 only Gemini and Applebot run JavaScript), can the four retrieval bots reach it, is there an accidental noindex/nosnippet, does the canonical point at THIS domain (a leftover pointing at a Replit/Vercel/staging host silently de-indexes the real site), is the structured data valid AND mirrored in visible text, is the content structured so a single section survives being quoted, robots.txt + sitemap, and time-to-first-byte. Run it after building or changing any public page, and before telling the user their site is discoverable \u2014 reads on every plan. Two gates are reported as `skipped` because they need a headless browser or a full crawl; treat skipped as unknown, never as passing.",
44345
+ description: "Run the SEO + AEO build gates against ONE live page the user owns, fetched AS OAI-SearchBot (a retrieval bot, so it sees what ChatGPT search sees; training crawlers like GPTBot are blocked by design on Awesomate-managed domains). Returns pass/fail per gate with specific fixes: is the content in the raw HTML at all (a React/Vue site is invisible to ChatGPT, Claude and Perplexity \u2014 only Gemini and Applebot run JavaScript), can the four retrieval bots reach it, is there an accidental noindex/nosnippet, does the canonical point at THIS domain (a leftover pointing at a Replit/Vercel/staging host silently de-indexes the real site), is the structured data valid AND mirrored in visible text, is the content structured so a single section survives being quoted, robots.txt + sitemap, and time-to-first-byte. Run it after building or changing any public page, and before telling the user their site is discoverable \u2014 reads on every plan. Two gates are reported as `skipped` because they need a headless browser or a full crawl; treat skipped as unknown, never as passing.",
44436
44346
  inputSchema: {
44437
44347
  domain: external_exports.string().min(3).max(253).describe("a domain on the user's own hosting account"),
44438
44348
  path: external_exports.string().max(400).optional().describe("page path to audit, default /")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.87.0",
3
+ "version": "0.88.6",
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,6 +28,7 @@
28
28
  "zod": "^3.24.1"
29
29
  },
30
30
  "devDependencies": {
31
+ "@anthropic-ai/sdk": "^0.132.0",
31
32
  "@types/node": "^25.9.3",
32
33
  "esbuild": "^0.25.0",
33
34
  "typescript": "^6.0.3"
@@ -1,5 +1,48 @@
1
1
  {
2
2
  "versions": [
3
+ {
4
+ "version": "0.88.6",
5
+ "highlights": [
6
+ "An app whose setup failed no longer uses one of your app slots or blocks its name: create it again with the same name to retry, or ask Claude to remove it with the new awesomate_app_remove_failed tool"
7
+ ]
8
+ },
9
+ {
10
+ "version": "0.88.5",
11
+ "highlights": [
12
+ "Adding a domain now gives your server's public address, never an internal one, and says the records must be Proxied (orange cloud) in Cloudflare with SSL/TLS set to Full"
13
+ ]
14
+ },
15
+ {
16
+ "version": "0.88.4",
17
+ "highlights": [
18
+ "The site audit now reads your page the way ChatGPT search does, so a page that turns AI bots away is reported as unreachable instead of being scored on the block message, and the sitemap check no longer reports a sitemap missing when it is there",
19
+ "SEO checks test the AI search bots that decide whether you get cited. AI training bots are blocked on purpose on Awesomate-managed addresses, so Claude no longer reports that as a fault"
20
+ ]
21
+ },
22
+ {
23
+ "version": "0.88.3",
24
+ "highlights": [
25
+ "When the hub cannot bring your n8n credential list up to date, Claude now says the list may be out of date, as of when, and which credentials n8n has added or removed since"
26
+ ]
27
+ },
28
+ {
29
+ "version": "0.88.2",
30
+ "highlights": [
31
+ "Fixed: the booking diary tool described its opening hours in an older JSON Schema form that strict clients refuse, which would stop every Awesomate tool loading there. Nothing changes in Claude Code"
32
+ ]
33
+ },
34
+ {
35
+ "version": "0.88.1",
36
+ "highlights": [
37
+ "Bookings can keep a calendar's busy times free from Outlook as well as Google Calendar, and write each booking into it; the owner connects it in the hub"
38
+ ]
39
+ },
40
+ {
41
+ "version": "0.88.0",
42
+ "highlights": [
43
+ "Claude no longer builds workflows through Awesomate: it writes them straight on your own n8n through n8n's MCP server (switch on MCP access in your n8n and add its key to Claude Code), with no Awesomate limits on it. Reading your n8n, installing templates and n8n Agents work as before"
44
+ ]
45
+ },
3
46
  {
4
47
  "version": "0.87.0",
5
48
  "highlights": [
@@ -70,9 +70,10 @@ decision brain. The short version, matched to what they say:
70
70
  a static app — there is nowhere to put a secret. Anything the page must
71
71
  keep private (an API key, a webhook URL you don't want public) rules
72
72
  static out.
73
- - **`awesomate_n8n_attach_to_app` is Node-only.** So the "form posts to
74
- their n8n" pattern needs a Node app, unless the webhook URL being public
75
- is genuinely acceptable.
73
+ - **`awesomate_n8n_attach_to_app` is Node-only.** It takes any production
74
+ webhook URL on their n8n and stores it with a secret on the app, so the
75
+ "form posts to their n8n" pattern needs a Node app, unless the webhook
76
+ URL being public is genuinely acceptable.
76
77
  - **No separate dev site.** All three environment URLs point at the same
77
78
  single subdomain — there's no dev copy to review on, so every change is
78
79
  live the moment it's published.
@@ -125,7 +126,10 @@ target; never touch prod without an explicit ask.**
125
126
  webhooks/integrations → `node-api-only`; landing page → `static-landing`.
126
127
  It returns **202** with an `appId` + subdomain(s); poll `awesomate_app_get`
127
128
  until `status` is `active` (or `failed` — read `provision_error`, explain
128
- plainly).
129
+ plainly). A failed app holds no plan slot and doesn't keep its name: to
130
+ retry, call `awesomate_app_create` again with the same `appSlug` (the hub
131
+ clears the failed attempt first). If the user would rather drop it, offer
132
+ `awesomate_app_remove_failed` (`confirm` = the slug) after their yes.
129
133
  4. **Scaffold** — call `awesomate_app_scaffold` and write each returned file
130
134
  into a fresh local project folder at its relative path (placeholders are
131
135
  already substituted), then `npm install` (Node) and read the bundled
@@ -256,10 +260,11 @@ keep going on what you can fix.
256
260
  - A `409` with code `app_limit` = the plan's app allowance is full (Support
257
261
  Plus 5, Pro 20 — `awesomate_app_context.limits` has the live numbers).
258
262
  Don't retry; tell the user and surface the `recommendedPlan`/`deepLink`
259
- from the error. **There is no delete-app tool or route** — don't offer
260
- "delete an old one to free a slot", because neither you nor the user can.
261
- The honest options are an upgrade or asking support (awesomate-support
262
- skill) to remove an app.
263
+ from the error. Failed apps never count toward it. There is no tool to
264
+ delete a WORKING app — don't offer "delete an old one to free a slot",
265
+ because neither you nor the user can. The honest options are an upgrade or
266
+ asking support (awesomate-support skill) to remove an app.
267
+ (`awesomate_app_remove_failed` only removes an app whose setup failed.)
263
268
  - Never invent that a capability exists — read `awesomate_app_context` first.
264
269
  - Never build on the hosting account, and never raise a support ticket for a
265
270
  repo or deploy problem yourself: the hub does both the building (via
@@ -50,8 +50,9 @@ with a clear reason). Postgres code should prefer `DATABASE_URL`.
50
50
  ## Attach n8n as the backend
51
51
 
52
52
  Job needs email / a lookup in their connected apps / AI → build the webhook
53
- workflow with the awesomate-n8n skill, then `awesomate_n8n_attach_to_app`
54
- (stores `N8N_WEBHOOK_URL` + secret; returns the secret ONCE — add the
53
+ workflow with the awesomate-n8n skill (through the user's own n8n MCP
54
+ server), then `awesomate_n8n_attach_to_app` with its production webhook URL
55
+ (stores `N8N_WEBHOOK_URL` + secret; returns the secret ONCE: add the
55
56
  matching `X-Awesomate-Webhook-Secret` header check to the workflow). The app
56
57
  calls `callWorkflow(payload)` from `src/lib/n8n.ts`; it no-ops when unset and
57
58
  never throws — check `result.ok` and degrade gracefully.
@@ -65,13 +65,14 @@ credentials are already connected there, so there's nothing new to set up.
65
65
  - Check what's connected with `awesomate_n8n_inspect {what:'credentials'}`
66
66
  (names/types only) and offer what they already have: *"You've got Gmail +
67
67
  Google Sheets connected — want the form to email you and add the lead to a
68
- sheet?"* That read needs the **"Build and change automations from Claude Code"**
68
+ sheet?"* That read needs the **"Let Claude Code work with your automations"**
69
69
  privacy toggle, which is off by default — a 403 `consent_required` means
70
70
  call `awesomate_privacy_settings`, name the toggle, and ask the user to flip
71
71
  it at hub.awesomate.ai/settings?tab=privacy.
72
72
  - Build the workflow with the **awesomate-n8n** skill (webhook trigger →
73
- action), take its **webhook URL**, and wire it into the app.
74
- - **Node app:** `awesomate_n8n_attach_to_app` — it stores
73
+ action, written through the user's own n8n MCP server), take its
74
+ **production webhook URL**, and wire it into the app.
75
+ - **Node app:** `awesomate_n8n_attach_to_app` with that URL. It stores
75
76
  `N8N_WEBHOOK_URL` plus a generated `N8N_WEBHOOK_SECRET` (encrypted +
76
77
  injected) and returns the secret so you can add the matching
77
78
  `X-Awesomate-Webhook-Secret` check on the n8n side. `src/lib/n8n.ts`
@@ -82,9 +82,12 @@ themselves cannot be written through `awesomate_crm_write`; only these actions c
82
82
  Essentials, the tool answers `upgrade_required`: say the owner can do the same in the hub under
83
83
  Contacts, Bookings, and do not retry.
84
84
  - Payments and deposits are not part of bookings yet. Do not promise them; the price is shown only.
85
- - **Google Calendar** is connected per calendar in the hub (Contacts, Bookings, Set up, Connect
86
- Google Calendar), by the person whose calendar it is: Claude cannot connect it, so send them
87
- there. Once connected, times they are busy in Google are never offered, and every booking is
88
- written to an "Awesomate bookings" calendar in their Google account, moved and removed with it.
89
- `setup` lists the connected calendars under `links`. Outlook is not available yet; there the
90
- notice email's invite is how a booking reaches their calendar.
85
+ - **Google Calendar or Outlook** is connected per calendar in the hub (Contacts, Bookings, Set
86
+ up, Connect Google Calendar or Connect Outlook), by the person whose calendar it is: Claude
87
+ cannot connect it, so send them there. Once connected, times they are busy there are never
88
+ offered, and every booking is written to an "Awesomate bookings" calendar in their account,
89
+ moved and removed with it. `setup` lists the connected calendars under `links` (each with its
90
+ `provider`), and says which providers this hub offers (`google.available`,
91
+ `microsoft.available`). Where neither is offered, the notice email's invite is how a booking
92
+ reaches their calendar. A work account whose organisation limits apps may need its Microsoft
93
+ 365 admin to approve Awesomate once; the hub says so when that happens.