@awesomate/hosting-mcp 0.12.0 → 0.13.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.
Files changed (58) hide show
  1. package/dist/index.js +204 -8
  2. package/package.json +2 -2
  3. package/skill/awesomate-app-builder/SKILL.md +5 -1
  4. package/skill/awesomate-credentials/SKILL.md +1 -1
  5. package/skill/awesomate-hosting/SKILL.md +50 -159
  6. package/skill/awesomate-hosting/references/connect-troubleshooting.md +77 -0
  7. package/skill/awesomate-hosting/references/multi-account.md +33 -0
  8. package/skill/awesomate-hosting/references/rest-fallback.md +30 -0
  9. package/skill/awesomate-hosting/scripts/bootstrap.mjs +27 -7
  10. package/skill/awesomate-n8n/SKILL.md +153 -147
  11. package/skill/awesomate-n8n/evals/ai-agent-build/graders/grader.md +31 -0
  12. package/skill/awesomate-n8n/evals/ai-agent-build/prompt.md +1 -0
  13. package/skill/awesomate-n8n/evals/build-form-email/graders/grader.md +30 -0
  14. package/skill/awesomate-n8n/evals/build-form-email/prompt.md +1 -0
  15. package/skill/awesomate-n8n/evals/datatable-dedupe/graders/grader.md +32 -0
  16. package/skill/awesomate-n8n/evals/datatable-dedupe/prompt.md +2 -0
  17. package/skill/awesomate-n8n/evals/diagnose-failure/graders/grader.md +27 -0
  18. package/skill/awesomate-n8n/evals/diagnose-failure/prompt.md +2 -0
  19. package/skill/awesomate-n8n/evals/essentials-upsell/graders/grader.md +27 -0
  20. package/skill/awesomate-n8n/evals/essentials-upsell/prompt.md +2 -0
  21. package/skill/awesomate-n8n/evals/live-change-promote/graders/grader.md +35 -0
  22. package/skill/awesomate-n8n/evals/live-change-promote/prompt.md +2 -0
  23. package/skill/awesomate-n8n/evals/possibilities-grounded/graders/grader.md +28 -0
  24. package/skill/awesomate-n8n/evals/possibilities-grounded/prompt.md +1 -0
  25. package/skill/awesomate-n8n/evals/validated-not-done/graders/grader.md +27 -0
  26. package/skill/awesomate-n8n/evals/validated-not-done/prompt.md +2 -0
  27. package/skill/awesomate-n8n/evals/vars-not-env/graders/grader.md +26 -0
  28. package/skill/awesomate-n8n/evals/vars-not-env/prompt.md +2 -0
  29. package/skill/awesomate-n8n/evals/webhook-body-fix/graders/grader.md +26 -0
  30. package/skill/awesomate-n8n/evals/webhook-body-fix/prompt.md +3 -0
  31. package/skill/awesomate-n8n/references/ai-agents.md +135 -0
  32. package/skill/awesomate-n8n/references/datatables.md +105 -0
  33. package/skill/awesomate-n8n/references/{node-recipes.md → platform-notes.md} +56 -7
  34. package/skill/awesomate-n8n/references/possibilities.md +83 -0
  35. package/skill/awesomate-n8n/references/testing-policy.md +115 -0
  36. package/skill/awesomate-n8n/references/troubleshooting.md +69 -0
  37. package/skill/awesomate-n8n/references/upgrade-loop.md +98 -0
  38. package/skill/awesomate-n8n/references/vendor/MANIFEST.json +26 -0
  39. package/skill/awesomate-n8n/references/vendor/code-node/BUILTIN_FUNCTIONS.md +779 -0
  40. package/skill/awesomate-n8n/references/vendor/code-node/COMMON_PATTERNS.md +1123 -0
  41. package/skill/awesomate-n8n/references/vendor/code-node/DATA_ACCESS.md +797 -0
  42. package/skill/awesomate-n8n/references/vendor/code-node/ERROR_PATTERNS.md +776 -0
  43. package/skill/awesomate-n8n/references/vendor/code-node/SKILL.md +703 -0
  44. package/skill/awesomate-n8n/references/vendor/expressions/COMMON_MISTAKES.md +406 -0
  45. package/skill/awesomate-n8n/references/vendor/expressions/EXAMPLES.md +496 -0
  46. package/skill/awesomate-n8n/references/vendor/expressions/SKILL.md +525 -0
  47. package/skill/awesomate-n8n/references/vendor/node-configuration/DEPENDENCIES.md +743 -0
  48. package/skill/awesomate-n8n/references/vendor/node-configuration/OPERATION_PATTERNS.md +926 -0
  49. package/skill/awesomate-n8n/references/vendor/node-configuration/SKILL.md +583 -0
  50. package/skill/awesomate-n8n/references/vendor/validation/ERROR_CATALOG.md +781 -0
  51. package/skill/awesomate-n8n/references/vendor/validation/FALSE_POSITIVES.md +695 -0
  52. package/skill/awesomate-n8n/references/vendor/validation/SKILL.md +414 -0
  53. package/skill/awesomate-n8n/references/vendor/workflow-patterns/SKILL.md +413 -0
  54. package/skill/awesomate-n8n/references/vendor/workflow-patterns/ai_agent_workflow.md +797 -0
  55. package/skill/awesomate-n8n/references/vendor/workflow-patterns/database_operations.md +798 -0
  56. package/skill/awesomate-n8n/references/vendor/workflow-patterns/http_api_integration.md +747 -0
  57. package/skill/awesomate-n8n/references/vendor/workflow-patterns/scheduled_tasks.md +786 -0
  58. package/skill/awesomate-n8n/references/vendor/workflow-patterns/webhook_processing.md +558 -0
package/dist/index.js CHANGED
@@ -39676,9 +39676,9 @@ var StdioServerTransport = class {
39676
39676
 
39677
39677
  // src/index.ts
39678
39678
  import { readFileSync as readFileSync2 } from "node:fs";
39679
- import { homedir as homedir2 } from "node:os";
39680
- import { join as join2, dirname as dirname2 } from "node:path";
39681
- import { fileURLToPath } from "node:url";
39679
+ import { homedir as homedir3 } from "node:os";
39680
+ import { join as join3, dirname as dirname3 } from "node:path";
39681
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
39682
39682
 
39683
39683
  // src/config.ts
39684
39684
  import { existsSync, readFileSync } from "node:fs";
@@ -39910,10 +39910,39 @@ function hubGet(config3, path) {
39910
39910
  function hubPost(config3, path, jsonBody = {}) {
39911
39911
  return hubRequest(config3, "POST", path, jsonBody);
39912
39912
  }
39913
+ function hubPatch(config3, path, jsonBody = {}) {
39914
+ return hubRequest(config3, "PATCH", path, jsonBody);
39915
+ }
39913
39916
  function hubDelete(config3, path) {
39914
39917
  return hubRequest(config3, "DELETE", path);
39915
39918
  }
39916
39919
 
39920
+ // src/skills.ts
39921
+ import { cpSync, existsSync as existsSync2, readdirSync, writeFileSync } from "node:fs";
39922
+ import { homedir as homedir2 } from "node:os";
39923
+ import { join as join2, dirname as dirname2 } from "node:path";
39924
+ import { fileURLToPath } from "node:url";
39925
+ var PACKAGE_ROOT = join2(dirname2(fileURLToPath(import.meta.url)), "..");
39926
+ function installSkills(version2) {
39927
+ const sourceRoot = join2(PACKAGE_ROOT, "skill");
39928
+ const destRoot = join2(homedir2(), ".claude", "skills");
39929
+ const installed = [];
39930
+ for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
39931
+ if (!entry.isDirectory()) continue;
39932
+ const src = join2(sourceRoot, entry.name);
39933
+ if (!existsSync2(join2(src, "SKILL.md"))) continue;
39934
+ const dest = join2(destRoot, entry.name);
39935
+ cpSync(src, dest, { recursive: true });
39936
+ try {
39937
+ writeFileSync(join2(dest, ".installed-version"), `${version2}
39938
+ `);
39939
+ } catch {
39940
+ }
39941
+ installed.push(entry.name);
39942
+ }
39943
+ return { installed, version: version2, skillsDir: destRoot };
39944
+ }
39945
+
39917
39946
  // src/index.ts
39918
39947
  var config2 = null;
39919
39948
  var configError = null;
@@ -39930,7 +39959,7 @@ function accountStamp() {
39930
39959
  var SERVER_VERSION = (() => {
39931
39960
  try {
39932
39961
  const pkg = JSON.parse(
39933
- readFileSync2(join2(dirname2(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8")
39962
+ readFileSync2(join3(dirname3(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf8")
39934
39963
  );
39935
39964
  return pkg.version ?? "0.0.0";
39936
39965
  } catch {
@@ -39941,7 +39970,7 @@ function skillUpdateInfo() {
39941
39970
  let installedSkillVersion = null;
39942
39971
  try {
39943
39972
  installedSkillVersion = readFileSync2(
39944
- join2(homedir2(), ".claude", "skills", "awesomate-hosting", ".installed-version"),
39973
+ join3(homedir3(), ".claude", "skills", "awesomate-hosting", ".installed-version"),
39945
39974
  "utf8"
39946
39975
  ).trim() || null;
39947
39976
  } catch {
@@ -40016,15 +40045,15 @@ server.registerTool(
40016
40045
  );
40017
40046
  readTool(
40018
40047
  "awesomate_n8n_context",
40019
- "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. Companion reads once consented: GET /api/my-n8n/machine/credentials (credential names/types \u2014 never secrets), /credentials/schema/:type, /variables, plus the /api/my-n8n workflows and executions endpoints.",
40048
+ "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).",
40020
40049
  "/api/my-n8n/machine/context"
40021
40050
  );
40022
40051
  server.registerTool(
40023
40052
  "awesomate_n8n_deploy",
40024
40053
  {
40025
- 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), '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.",
40054
+ 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.",
40026
40055
  inputSchema: {
40027
- action: external_exports.enum(["validate", "create_draft", "activate", "deactivate", "promote", "rollback", "delete_draft"]),
40056
+ action: external_exports.enum(["validate", "create_draft", "update_draft", "activate", "deactivate", "promote", "rollback", "delete_draft"]),
40028
40057
  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)."),
40029
40058
  workflowId: external_exports.string().optional().describe("Required for activate/deactivate/delete_draft; for promote this is the LIVE workflow id"),
40030
40059
  draftId: external_exports.string().optional().describe("promote only: the tested draft to swap into the live workflow"),
@@ -40039,6 +40068,13 @@ server.registerTool(
40039
40068
  if (action === "create_draft") {
40040
40069
  return textResult(await hubPost(requireConfig(), "/api/my-n8n/machine/workflows/draft", workflowJson ?? {}));
40041
40070
  }
40071
+ if (action === "update_draft") {
40072
+ if (!workflowId) return errorResult(new Error("workflowId is required for update_draft"));
40073
+ if (!workflowJson) return errorResult(new Error("workflowJson (full: name, nodes, connections, settings?) is required for update_draft"));
40074
+ return textResult(
40075
+ await hubPatch(requireConfig(), `/api/my-n8n/machine/workflows/${encodeURIComponent(workflowId)}`, workflowJson)
40076
+ );
40077
+ }
40042
40078
  if (action === "rollback") {
40043
40079
  if (!operationId) return errorResult(new Error("operationId is required for rollback"));
40044
40080
  return textResult(
@@ -40086,6 +40122,166 @@ server.registerTool(
40086
40122
  }
40087
40123
  }
40088
40124
  );
40125
+ server.registerTool(
40126
+ "awesomate_n8n_workflows",
40127
+ {
40128
+ 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.",
40129
+ inputSchema: {
40130
+ id: external_exports.string().optional().describe("Workflow id for a single-workflow read; omit for the all-workflows summary"),
40131
+ detail: external_exports.enum(["full", "structure"]).optional().describe("Single-workflow only. 'structure' strips node parameters"),
40132
+ active: external_exports.enum(["1", "0"]).optional().describe("Summary only: filter by active state"),
40133
+ limit: external_exports.number().optional(),
40134
+ offset: external_exports.number().optional()
40135
+ }
40136
+ },
40137
+ async ({ id, detail, active, limit, offset }) => {
40138
+ try {
40139
+ if (!id) {
40140
+ const qs = new URLSearchParams();
40141
+ if (active) qs.set("active", active);
40142
+ if (limit) qs.set("limit", String(limit));
40143
+ if (offset) qs.set("offset", String(offset));
40144
+ const q = qs.toString();
40145
+ return textResult(await hubGet(requireConfig(), `/api/my-n8n/machine/workflows${q ? `?${q}` : ""}`));
40146
+ }
40147
+ const full = await hubGet(requireConfig(), `/api/my-n8n/workflows/${encodeURIComponent(id)}`);
40148
+ if (detail === "structure" && full?.workflow?.nodes) {
40149
+ full.workflow.nodes = full.workflow.nodes.map((n) => {
40150
+ const { parameters: _p, ...rest } = n;
40151
+ return rest;
40152
+ });
40153
+ }
40154
+ return textResult(full);
40155
+ } catch (err) {
40156
+ return errorResult(err);
40157
+ }
40158
+ }
40159
+ );
40160
+ server.registerTool(
40161
+ "awesomate_n8n_inspect",
40162
+ {
40163
+ 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.`,
40164
+ inputSchema: {
40165
+ what: external_exports.enum(["nodes", "datatables", "datatable_rows", "possibilities", "credentials", "variables"]),
40166
+ datatableId: external_exports.string().optional().describe("datatable_rows only"),
40167
+ limit: external_exports.number().optional().describe("datatable_rows only (max 100)")
40168
+ }
40169
+ },
40170
+ async ({ what, datatableId, limit }) => {
40171
+ try {
40172
+ const cfg = requireConfig();
40173
+ if (what === "datatable_rows") {
40174
+ if (!datatableId) return errorResult(new Error("datatableId is required for datatable_rows"));
40175
+ const q = limit ? `?limit=${limit}` : "";
40176
+ return textResult(await hubGet(cfg, `/api/my-n8n/machine/datatables/${encodeURIComponent(datatableId)}/rows${q}`));
40177
+ }
40178
+ const path = {
40179
+ nodes: "/api/my-n8n/machine/nodes",
40180
+ datatables: "/api/my-n8n/machine/datatables",
40181
+ possibilities: "/api/my-n8n/machine/possibilities",
40182
+ credentials: "/api/my-n8n/machine/credentials",
40183
+ variables: "/api/my-n8n/machine/variables"
40184
+ }[what];
40185
+ return textResult(await hubGet(cfg, path));
40186
+ } catch (err) {
40187
+ return errorResult(err);
40188
+ }
40189
+ }
40190
+ );
40191
+ server.registerTool(
40192
+ "awesomate_n8n_executions",
40193
+ {
40194
+ description: "Execution reads. workflowId \u2192 recent executions of that workflow. executionId \u2192 full detail with error summary. 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.",
40195
+ inputSchema: {
40196
+ workflowId: external_exports.string().optional(),
40197
+ executionId: external_exports.string().optional(),
40198
+ debug: external_exports.boolean().optional()
40199
+ }
40200
+ },
40201
+ async ({ workflowId, executionId, debug }) => {
40202
+ try {
40203
+ const cfg = requireConfig();
40204
+ if (executionId && debug) {
40205
+ return textResult(await hubGet(cfg, `/api/my-n8n/machine/executions/${encodeURIComponent(executionId)}/debug`));
40206
+ }
40207
+ if (executionId) {
40208
+ return textResult(await hubGet(cfg, `/api/my-n8n/executions/${encodeURIComponent(executionId)}`));
40209
+ }
40210
+ if (workflowId) {
40211
+ return textResult(await hubGet(cfg, `/api/my-n8n/workflows/${encodeURIComponent(workflowId)}/executions`));
40212
+ }
40213
+ return errorResult(new Error("Pass workflowId (list) or executionId (detail; add debug:true for node-by-node)"));
40214
+ } catch (err) {
40215
+ return errorResult(err);
40216
+ }
40217
+ }
40218
+ );
40219
+ server.registerTool(
40220
+ "awesomate_n8n_node_docs",
40221
+ {
40222
+ 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.",
40223
+ inputSchema: {
40224
+ tool: external_exports.enum(["search_nodes", "get_node", "search_templates", "get_template", "validate_node", "tools_documentation"]),
40225
+ args: external_exports.record(external_exports.unknown()).optional().describe("Arguments passed to the catalog tool verbatim")
40226
+ }
40227
+ },
40228
+ async ({ tool, args }) => {
40229
+ try {
40230
+ return textResult(await hubPost(requireConfig(), `/api/my-n8n/machine/node-catalog/${tool}`, args ?? {}));
40231
+ } catch (err) {
40232
+ return errorResult(err);
40233
+ }
40234
+ }
40235
+ );
40236
+ server.registerTool(
40237
+ "awesomate_n8n_datatable_write",
40238
+ {
40239
+ 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.",
40240
+ inputSchema: {
40241
+ action: external_exports.enum(["create", "add_column", "insert", "update", "delete_rows"]),
40242
+ tableId: external_exports.string().optional(),
40243
+ name: external_exports.string().optional().describe("create: table name; add_column: column name"),
40244
+ columns: external_exports.array(external_exports.object({ name: external_exports.string(), type: external_exports.enum(["string", "number", "boolean", "date"]).optional() })).optional(),
40245
+ workflowId: external_exports.string().optional().describe("create: co-locate the table with this workflow (recommended)"),
40246
+ type: external_exports.enum(["string", "number", "boolean", "date"]).optional().describe("add_column only"),
40247
+ rows: external_exports.array(external_exports.record(external_exports.unknown())).optional().describe("insert only"),
40248
+ filter: external_exports.record(external_exports.unknown()).optional().describe("update/delete_rows: {filters:[{columnName,condition,value}], type?}"),
40249
+ data: external_exports.record(external_exports.unknown()).optional().describe("update only: column \u2192 new value")
40250
+ }
40251
+ },
40252
+ async ({ action, tableId, name, columns, workflowId, type, rows, filter, data }) => {
40253
+ try {
40254
+ const cfg = requireConfig();
40255
+ if (action === "create") {
40256
+ return textResult(await hubPost(cfg, "/api/my-n8n/machine/datatables", { name, columns, workflowId }));
40257
+ }
40258
+ if (!tableId) return errorResult(new Error(`tableId is required for ${action}`));
40259
+ const base = `/api/my-n8n/machine/datatables/${encodeURIComponent(tableId)}`;
40260
+ if (action === "add_column") {
40261
+ return textResult(await hubPost(cfg, `${base}/columns`, { name, type }));
40262
+ }
40263
+ const mode = action === "delete_rows" ? "delete" : action;
40264
+ return textResult(await hubPost(cfg, `${base}/rows`, { mode, rows, filter, data }));
40265
+ } catch (err) {
40266
+ return errorResult(err);
40267
+ }
40268
+ }
40269
+ );
40270
+ server.registerTool(
40271
+ "awesomate_skill_update",
40272
+ {
40273
+ description: "Refresh the locally installed Awesomate skills from this (always-latest) server package \u2014 run when awesomate_get_context reports skill.updateAvailable. Copies every bundled skill into ~/.claude/skills and re-stamps versions. New skill content applies from the NEXT Claude Code session; finish the current task first, then suggest a restart.",
40274
+ inputSchema: {}
40275
+ },
40276
+ async () => {
40277
+ try {
40278
+ const result = installSkills(SERVER_VERSION);
40279
+ return textResult({ ...result, note: "Updated skill files load in the next Claude Code session." });
40280
+ } catch (err) {
40281
+ return errorResult(err);
40282
+ }
40283
+ }
40284
+ );
40089
40285
  readTool(
40090
40286
  "awesomate_get_hosting_status",
40091
40287
  "The hosting account's provisioning state: eligible/provisioned flags, in-progress provisioning step, primary domain, cPanel server, DNS targets. Use before suggesting any site action.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.12.0",
4
- "description": "Awesomate MCP server lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
3
+ "version": "0.13.0",
4
+ "description": "Awesomate MCP server \u2014 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",
7
7
  "publishConfig": {
@@ -52,7 +52,7 @@ machinery that does it.
52
52
  If the app needs to **send an email, look something up in their apps (Sheets,
53
53
  CRM, a database), or use AI** — their **own n8n** is often the fastest path,
54
54
  because their credentials are already connected there. Check the credential
55
- inventory (`GET /api/my-n8n/machine/credentials`) and say e.g. *"you already
55
+ inventory (`awesomate_n8n_inspect {what:'credentials'}`) and say e.g. *"you already
56
56
  have Gmail connected in n8n — want the form to email you through that?"* Use
57
57
  the **awesomate-n8n** skill to build the workflow, then store its webhook URL
58
58
  in the app (see the awesomate-credentials skill) and call it from the app.
@@ -118,6 +118,10 @@ commit + push (redeploys the previous good state), or redeploy an earlier good
118
118
  SHA. Always `awesomate_app_health` after a promote. Static sites ship their
119
119
  files directly (no build step).
120
120
 
121
+ When the need is "remember/track a list an automation uses" rather than an
122
+ app with a UI, an n8n data table (awesomate-n8n skill) beats building an app
123
+ — no hosting slot, no deploy, queryable from workflows directly.
124
+
121
125
  ## 3. Hard rules
122
126
 
123
127
  - Non-technical user: explain in plain words, one or two short questions at a
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: awesomate-credentials
3
- description: Safely capture and store API keys, tokens, database URLs and other secrets for the user's Awesomate apps — encrypted in the hub and injected into the app's .env, never committed to git, never echoed back. Use whenever the user pastes something that looks like a secret, says "here's my API key", "add this key", "store this secret / token / password", "save my credentials", or drops a key into a file. Companion to awesomate-app-builder.
3
+ description: Safely capture and store API keys, tokens, database URLs and other secrets for the user's Awesomate apps — encrypted in the hub and injected into the app's .env, never committed to git, never echoed back. Use whenever the user pastes something that looks like a secret, says "here's my API key", "add this key", "store this secret / token / password", "save my credentials", or drops a key into a file. Companion to awesomate-app-builder. If the secret is for an n8n workflow or automation, route to the awesomate-n8n skill instead — n8n credentials are created on the n8n instance, never in .env.
4
4
  ---
5
5
 
6
6
  # Awesomate Credentials — handle secrets safely
@@ -26,127 +26,28 @@ generation, defer to WordPress Studio and any installed WordPress skills
26
26
 
27
27
  ## 0. First run — orient before acting
28
28
 
29
- On the first hosting request in a session, call **`awesomate_whoami`** — it's
30
- zero-network and tells you WHICH account this folder is connected to and why
31
- (`source`: a `.awesomate.json` folder pin, `AWESOMATE_ACCOUNT`, the sole
32
- profile, or the default profile). **If the slug isn't the account the user is
33
- talking about, stop and fix the connection before doing any work** — every
34
- subsequent tool response is stamped with `account: <slug> (source: )`; treat a
35
- mismatch there the same way. Then call **`awesomate_get_context`** once (then
36
- `awesomate_get_limits` if you're about to create/change anything). From the
37
- context note three things and cache them for the session:
38
-
39
- - **`plan`** and **`capabilities.shell`** — this decides which branch below you're on.
40
- - **`patExpiresAt`** if it's within ~7 days, tell the user to open
41
- **hub.awesomate.ai/sites Connect Claude Code** and re-run the setup prompt to
42
- refresh the token. If any tool returns a 401, do the same.
43
- - **`skill.updateAvailable`** — if true, this skill's local files are older than
44
- the MCP server. Mention it once (don't nag): re-running Connect Claude Code
45
- from **hub.awesomate.ai/sites** refreshes the skill and renews the token in
46
- one go. Not urgent — finish the user's actual request first.
29
+ On the first hosting request in a session, call **`awesomate_whoami`** —
30
+ zero-network; it reports WHICH account this folder is connected to and why.
31
+ **If the slug isn't the account the user means, stop and fix the connection
32
+ before any work** every tool response is stamped `account: <slug> (source:
33
+ …)`; treat a mismatch there the same way. Then call **`awesomate_get_context`**
34
+ once (plus `awesomate_get_limits` before any create/change) and cache:
35
+
36
+ - **`plan`** and **`capabilities.shell`** decides which branch below you're on.
37
+ - **`patExpiresAt`** within ~7 days, or any 401: re-run the setup prompt at
38
+ **hub.awesomate.ai/sites → Connect Claude Code** to refresh the token.
39
+ - **`skill.updateAvailable`** — this skill's local files are older than the MCP
40
+ server. Mention once (don't nag); the same Connect re-run refreshes the skill
41
+ and renews the token in one go. Not urgent — finish the user's request first.
47
42
  - **`cpanel`** routing (host/user), present once hosting is provisioned.
48
43
 
49
44
  If `awesomate_get_context` fails with a connectivity error, it's the user's
50
45
  network or the API base — not an auth problem; say so.
51
46
 
52
- ### Multiple accounts on one machine
53
-
54
- `~/.awesomate/credentials.json` can hold several account **profiles** (one per
55
- slug), and each project folder is bound to one via a committable
56
- `.awesomate.json` pin (`{"account": "<slug>"}`) written by the bootstrap in the
57
- folder it ran in. Resolution order: `AWESOMATE_PAT` env → `AWESOMATE_ACCOUNT`
58
- env → nearest folder pin (walking up, stopping at `$HOME`) → sole profile →
59
- `defaultProfile`. A pin naming a missing profile is a **hard error, never a
60
- fallback** — that's deliberate.
61
-
62
- - **Wrong account connected?** Compare `awesomate_whoami` against the folder's
63
- `.awesomate.json`. Fix by editing the pin (if the profile exists) or by
64
- running Connect Claude Code from the hub **while logged into the right hub
65
- account, from the right folder** — the connect command carries
66
- `--account <slug>` and the bootstrap aborts on a mismatch instead of storing
67
- anything.
68
- - **"Multiple accounts … folder isn't pinned" error?** Create the pin file or
69
- set `AWESOMATE_ACCOUNT`. Don't guess an account for the user.
70
- - Connecting a second account never disconnects the first; re-pairing an
71
- account just refreshes its profile.
72
- - The bootstrap prints a final `AWESOMATE CONNECT: SUCCESS account=<slug>` (or
73
- `PARTIAL … issues=…` / `FAILED reason=…`) sentinel — when a connect ran as a
74
- `!` bang command, check for that line instead of assuming success from
75
- silence. `PARTIAL` means the token/pin are stored but a step (listed in
76
- `issues=`) needs the printed manual fix.
77
-
78
- ### Sandboxed / ephemeral environments (cloud agent containers)
79
-
80
- - **Egress proxy**: if `HTTPS_PROXY`/`HTTP_PROXY` is set, the environment
81
- forces traffic through a proxy that Node's built-in fetch ignores — requests
82
- bypass it and the gateway answers 403, which looks like a bad code/token but
83
- is a transport failure. The bootstrap and MCP server handle this themselves
84
- (undici env-proxy agent); if an older version is in play, prefix commands
85
- with `NODE_USE_ENV_PROXY=1`. Diagnostic: `curl` works but `node -e "fetch(…)"`
86
- 403s → proxy bypass, not credentials.
87
- - **Interrupted connect?** Setup codes are re-runnable for their whole
88
- 10-minute life (each run mints a fresh token), and `--pat amt_pat_…` resumes
89
- with an already-redeemed token — never tell the user their code is spent
90
- unless the API literally said so.
91
- - **Nothing persists**: `~/.awesomate`, the skill copy, and the MCP
92
- registration all vanish with the container. Treat a container connect as a
93
- diagnosis/test, and tell the user a real workstation is needed for a lasting
94
- install.
95
- - **No ssh client**: containers usually lack `ssh-keygen`/`ssh`, so deploy and
96
- shell are unavailable — reads and management calls still work. The bootstrap
97
- skips SSH with a note rather than failing.
98
- - Never probe connectivity with a POST to `/redeem` — it's state-changing. Use
99
- `GET {apiBase}/api/hosting-access/context` (401 without a token still proves
100
- reachability).
101
-
102
- ### When you're stuck: generate a support report
103
-
104
- If a connect or tool failure survives the documented fixes above (wrong
105
- account, proxy, resume, restart, legacy registrations), don't keep guessing —
106
- hand Awesomate a diagnostic they can act on:
107
-
108
- 1. Run `node ~/.claude/skills/awesomate-hosting/scripts/support-report.mjs
109
- --note "<one line: what the user was doing and what happened>"`.
110
- It writes a **fully redacted** report to
111
- `~/.awesomate/support-report-<timestamp>.md` — tokens reduced to
112
- prefix+last4, no key material, proxy credentials stripped. It includes
113
- versions, profiles/pin/registration state, live connectivity probes, and
114
- the last bootstrap log automatically.
115
- 2. Show the user the file path and the headline findings (the ACTIVE line,
116
- any ⚠ LEGACY registration flags, and the probe results).
117
- 3. **Ask the user before submitting.** With their OK, re-run with `--submit`
118
- — it POSTs the report to Awesomate and returns a reference like
119
- `ASR-XXXXXXXX`. Tell the user to quote that reference to
120
- support@awesomate.ai or their Awesomate contact; the report is already
121
- attached to it server-side. Submission works even when the token is
122
- broken (that's usually why you're here).
123
- 4. If `--submit` fails too (fully offline), the user emails the file itself —
124
- it's safe to send as-is.
125
-
126
- Never edit the report to add raw tokens, codes, or keys, and never submit
127
- without the user's explicit go-ahead.
128
-
129
- If this skill is loaded but **no `awesomate_*` tools exist in the session at
130
- all**, the MCP server was registered after Claude Code started (the bootstrap
131
- just ran). Don't investigate settings files or reinstall anything — and don't
132
- make a restart the precondition for showing the user their own data. The
133
- skill works right now over REST — resolve the ACTIVE profile first (run
134
- `node ~/.claude/skills/awesomate-hosting/scripts/resolve-account.mjs --api` to
135
- get `API`/`PAT`/`ACCT`; never read top-level `pat` directly when a `profiles`
136
- map exists, and never echo the pat into the conversation) and GET with
137
- `Authorization: Bearer <pat>`:
138
-
139
- | MCP tool | REST equivalent |
140
- |---|---|
141
- | `awesomate_get_context` | `GET {apiBase}/api/hosting-access/context` |
142
- | `awesomate_get_hosting_status` | `GET {apiBase}/api/client-hosting/status` |
143
- | `awesomate_get_hosting_account` | `GET {apiBase}/api/client-hosting/account` |
144
- | `awesomate_list_sites` | `GET {apiBase}/api/client-hosting/sites` |
145
- | `awesomate_list_domains` | `GET {apiBase}/api/client-hosting/domains` |
146
- | `awesomate_get_limits` | `GET {apiBase}/api/client-hosting/limits` |
147
-
148
- Serve the request with these now, then suggest restarting Claude Code at a
149
- natural break so the richer MCP integration loads.
47
+ References: `references/multi-account.md` (profiles, pins, resolution order,
48
+ wrong account) · `references/connect-troubleshooting.md` (bootstrap, sandbox/
49
+ proxy failures, resuming a connect, support report) ·
50
+ `references/rest-fallback.md` (no `awesomate_*` tools serve over REST).
150
51
 
151
52
  ## 1. The plan model (so your nudges are accurate)
152
53
 
@@ -157,36 +58,35 @@ natural break so the richer MCP integration loads.
157
58
  | **Pro** | 10 | 20 | 10 | **Yes** | + higher limits |
158
59
  | **Embedded** | 100 | 100 | 100 | **Yes** | agency tier, not generally available |
159
60
 
160
- Authoritative limits are always what `awesomate_get_limits` returns for THIS
161
- account (and `awesomate_get_plan_features` fetches the live ladder from the
162
- hub) — never quote the table above as fact; use it only to explain upgrades.
163
- App creation past the cap returns a 409 with code `app_limit` — don't retry;
164
- surface the recommended plan instead.
61
+ Authoritative limits are what `awesomate_get_limits` returns for THIS account
62
+ (`awesomate_get_plan_features` fetches the live ladder from the hub) — never
63
+ quote the table above as fact; use it only to explain upgrades. App creation
64
+ past the cap returns a 409 with code `app_limit` — don't retry; surface the
65
+ recommended plan instead.
165
66
 
166
67
  ## 2. Branch by capability
167
68
 
168
69
  ### Essentials (no shell) — read, report, nudge
169
70
 
170
- Allowed: `awesomate_get_context`, `_get_hosting_status`, `_get_hosting_account`,
171
- `_list_sites`, `_list_domains`, `_get_limits`, `_get_plan` / `_get_plan_features`,
172
- `_get_hosting_stats`, `_get_grafana_url`, `_get_credit_balance`, the billing
173
- preview/deep-link tools, and 1-click WP-admin / cPanel SSO links.
71
+ Allowed: `awesomate_get_context`, `_get_hosting_status` (hosting stats),
72
+ `_get_hosting_account`, `_list_sites`, `_list_domains`, `_get_limits` (plan
73
+ limits AND workflow/AI-editor credit balances), `_get_plan_features` (live plan
74
+ ladder), and 1-click WP-admin / cPanel SSO links. Billing/plan changes: §4.
174
75
 
175
76
  Never attempt SSH, WP-CLI, snapshot/rollback, staging, deploy, multi-site —
176
77
  those tools will 403. Instead, surface the specific limit and what
177
- Support Plus/Pro unlocks (see §4), and offer to preview the upgrade.
78
+ Support Plus/Pro unlocks (§4), and link the hub billing page.
178
79
 
179
80
  ### Support Plus and above (shell) — full workflow
180
81
 
181
82
  You additionally have: jailed SSH to the client's own cPanel account, WP-CLI
182
- (`awesomate_run_wp_cli` or raw SSH via `scripts/ssh-connect.sh`),
83
+ via `scripts/wp.sh` over that SSH (or raw SSH via `scripts/ssh-connect.sh`),
183
84
  snapshot/rollback, **WP staging on awesomate.dev** (§5a), and the local-first
184
85
  deploy flow (§5). Golden rule, always:
185
86
 
186
87
  > **Local first, live never — and snapshot before you touch live.**
187
88
 
188
- **Ask "live or dev?" before posting anything user-facing.** When the user asks
189
- you to change/publish something on their WordPress site, ask: *"Want me to put
89
+ **Ask "live or dev?" before posting anything user-facing** *"Want me to put
190
90
  this on your live site, or on your private dev copy (awesomate.dev) so you can
191
91
  review it first?"* Default suggestion: dev first, publish after they've looked
192
92
  at it. Their dev copy is invisible to Google and AI crawlers by policy.
@@ -196,27 +96,26 @@ at it. Their dev copy is invisible to Google and AI crawlers by policy.
196
96
  - **Before ANY change to a live site** — plugin/theme/core update, `search-replace`,
197
97
  bulk edit, deploy — call **`awesomate_snapshot_site`** first and tell the user the
198
98
  `snapshotId`. If it goes wrong, `awesomate_rollback_site` restores files + DB.
199
- - **Confirm before destructive actions**: `awesomate_uninstall_site`,
200
- `awesomate_rollback_site`, `awesomate_site_staging_promote` (replaces live),
201
- `awesomate_site_staging_discard` (loses unpublished staging work), dropping
202
- tables, `wp db reset`, deleting content.
203
- State exactly what will be lost and wait for an explicit "yes".
99
+ - **Confirm before destructive actions** `awesomate_rollback_site`,
100
+ `awesomate_site_staging_promote` (replaces live), `_site_staging_discard`
101
+ (loses unpublished staging work), dropping tables, `wp db reset`, deleting
102
+ content. State exactly what will be lost and wait for an explicit "yes".
103
+ - **Site deletion has no MCP tool.** The user deletes sites themselves at
104
+ **hub.awesomate.ai** — deep-link them there; never script around it.
204
105
  - Prefer building/testing in **WordPress Studio locally**, then deploy (§5).
205
106
  - Dry-run risky WP-CLI where the command supports it before the real run.
206
107
 
207
108
  ## 4. Limits & upgrade etiquette
208
109
 
209
110
  Before any create action (install a site, add a domain, a heavy build) call
210
- `awesomate_get_limits`. If a dimension is `approaching`/`exceeded`, or a tool
211
- returns 403 for a plan reason:
111
+ `awesomate_get_limits`. On `approaching`/`exceeded`, or a plan-reason 403:
212
112
 
213
- 1. Explain plainly what they've hit and what the next plan unlocks (use the
214
- nudge's `recommendedPlan`).
215
- 2. If they want to upgrade, **preview first** with the billing preview tool and
216
- show the exact prorated cost.
217
- 3. Only proceed to an execute/upgrade tool after an explicit in-chat "yes".
218
- **Never** trigger a charge, downgrade, or cancellation on your own —
219
- downgrades/cancels always go to the hub billing page via the deep-link tool.
113
+ 1. Explain plainly what they've hit and what the next plan unlocks (the
114
+ nudge's `recommendedPlan`; `awesomate_get_plan_features` has the live ladder).
115
+ 2. To change plan, deep-link `https://hub.awesomate.ai/billing`
116
+ (`?upgrade=<plan>` preselects). No billing tool exists in this MCP —
117
+ **never** trigger a charge, downgrade, or cancellation yourself; upgrades,
118
+ downgrades, and cancels all happen on the hub billing page.
220
119
 
221
120
  ## 5. Local-first deploy (Support Plus+)
222
121
 
@@ -270,25 +169,17 @@ Git skills drive the Git workflow; this skill just deploys the result.
270
169
 
271
170
  ## 7. Bundled scripts (`scripts/`)
272
171
 
273
- - `bootstrap.mjs` — one-time setup: redeems the hub setup code for a scoped
274
- access token, stores it locally (chmod 600), **installs this skill into
275
- `~/.claude/skills`**, registers the MCP server, and (Support Plus+, best-
276
- effort) generates an SSH keypair and registers the public key. If the SSH
277
- step fails (e.g. hosting not fully provisioned), the token + skill + MCP are
278
- still set up. The Connect-Claude-Code prompt from the hub runs this via
279
- `npx -y --package=@awesomate/hosting-mcp awesomate-hosting-bootstrap`.
280
- (Always the `--package=` form — a bare `-p` breaks the claude CLI's argv
281
- pre-scan if the command is ever wrapped in `claude mcp add`.)
172
+ - `bootstrap.mjs` — one-time setup: redeems the hub setup code for a scoped Run it with `--update` to refresh the installed skills from the current package without redeeming a new setup code.
173
+ token (chmod 600), **installs this skill into `~/.claude/skills`**, registers
174
+ the MCP server, best-effort SSH setup (Support Plus+). Invocation + failure
175
+ modes: `references/connect-troubleshooting.md`.
282
176
  - `ssh-connect.sh` — open a jailed SSH session (or run one remote command).
283
177
  - `wp.sh` — run a WP-CLI command against a live site over that SSH.
284
178
  - `deploy.sh` — snapshot-first Studio→live deploy (files, optional DB).
285
179
  - `pull-live.sh` — clone a live site down to a local folder (read-only on live).
286
- - `resolve-account.mjs` — shared account resolver (pin/env/profile precedence)
287
- used by the shell scripts and the REST fallback; `--api` emits API/PAT/ACCT,
288
- `--ssh` emits the ssh block.
180
+ - `resolve-account.mjs` — account resolver (pin/env/profile precedence) for
181
+ shell scripts + REST fallback; `--api` emits API/PAT/ACCT, `--ssh` ssh block.
289
182
  - `support-report.mjs` — redacted diagnostic bundle for Awesomate support
290
- (see "When you're stuck" above); `--submit` delivers it and returns a
291
- reference ID.
183
+ (`references/connect-troubleshooting.md`); `--submit` returns a reference ID.
292
184
 
293
- All scripts read `~/.awesomate/credentials.json`; none take secrets on the
294
- command line.
185
+ All scripts read `~/.awesomate/credentials.json`; none take secrets on the CLI.