@awesomate/hosting-mcp 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40790,6 +40790,107 @@ async function knowledgeCollections(config3, args) {
40790
40790
  return mapKnowledgeError(err, config3.apiBase);
40791
40791
  }
40792
40792
  }
40793
+ var faqEntryBody = (e) => ({
40794
+ question: e.question,
40795
+ answer: e.answer,
40796
+ ...e.altQuestions?.length ? { alt_questions: e.altQuestions } : {},
40797
+ ...e.category ? { category: e.category } : {},
40798
+ ...e.sourceUrl ? { source_url: e.sourceUrl } : {},
40799
+ ...e.externalKey ? { external_key: e.externalKey } : {}
40800
+ });
40801
+ async function knowledgeFaq(config3, args) {
40802
+ try {
40803
+ if (args.action === "sets") return await hubGet(config3, "/api/knowledge/faq/sets");
40804
+ if (args.action === "create_set") {
40805
+ if (!args.setTitle) return { error: "invalid_request", note: 'create_set needs setTitle, e.g. "Bayside Garden FAQ"' };
40806
+ return await hubPost(config3, "/api/knowledge/faq/sets", {
40807
+ title: args.setTitle,
40808
+ ...args.visibility ? { visibility: args.visibility } : {},
40809
+ ...args.collectionIds?.length ? { collection_ids: args.collectionIds } : {}
40810
+ });
40811
+ }
40812
+ if (args.action === "import") {
40813
+ if (!args.entries?.length) return { error: "invalid_request", note: "import needs entries [{question, answer}], at most 200 per call" };
40814
+ let setId = args.setId;
40815
+ if (!setId) {
40816
+ if (!args.setTitle) return { error: "invalid_request", note: "import needs setId, or setTitle to create or reuse the set by name" };
40817
+ const created = await hubPost(config3, "/api/knowledge/faq/sets", {
40818
+ title: args.setTitle,
40819
+ ...args.visibility ? { visibility: args.visibility } : {},
40820
+ ...args.collectionIds?.length ? { collection_ids: args.collectionIds } : {}
40821
+ });
40822
+ setId = created.set?.set_id;
40823
+ if (!setId) return { error: "unavailable", note: "The FAQ set could not be created; nothing was imported." };
40824
+ }
40825
+ const status = args.status === "published" ? "published" : "draft";
40826
+ const r = await hubPost(config3, `/api/knowledge/faq/sets/${encodeURIComponent(setId)}/entries`, {
40827
+ entries: args.entries.map(faqEntryBody),
40828
+ status,
40829
+ origin: "import",
40830
+ ...args.confirm ? { confirm: args.confirm } : {}
40831
+ });
40832
+ return {
40833
+ ...r,
40834
+ set_id: setId,
40835
+ note: status === "draft" ? "Imported as DRAFTS: nothing is live yet. Show the owner the count and a few samples, then ask ONE question: publish these to their assistant? On yes, call action publish {setId} (a public set also needs confirm: the account slug)." : "Published. The index job makes them searchable within a minute or two; then prove it with awesomate_knowledge_ask using a reworded question."
40836
+ };
40837
+ }
40838
+ if (args.action === "publish") {
40839
+ if (!args.setId) return { error: "invalid_request", note: "publish needs setId (from sets or the import result)" };
40840
+ return await hubPost(config3, `/api/knowledge/faq/sets/${encodeURIComponent(args.setId)}/publish`, {
40841
+ ...args.entryIds?.length ? { entry_ids: args.entryIds } : {},
40842
+ ...args.confirm ? { confirm: args.confirm } : {}
40843
+ });
40844
+ }
40845
+ if (args.action === "list") {
40846
+ if (!args.setId) return { error: "invalid_request", note: "list needs setId (from sets)" };
40847
+ const params = new URLSearchParams();
40848
+ if (args.status) params.set("status", args.status);
40849
+ if (args.q) params.set("q", args.q);
40850
+ if (args.cursor) params.set("cursor", args.cursor);
40851
+ if (args.limit) params.set("limit", String(args.limit));
40852
+ const qs = params.toString();
40853
+ return await hubGet(config3, `/api/knowledge/faq/sets/${encodeURIComponent(args.setId)}/entries${qs ? `?${qs}` : ""}`);
40854
+ }
40855
+ if (!args.entryId) return { error: "invalid_request", note: `${args.action} needs entryId (from list)` };
40856
+ const entryPath = `/api/knowledge/faq/entries/${encodeURIComponent(args.entryId)}`;
40857
+ if (args.action === "delete") {
40858
+ await hubDelete(config3, entryPath);
40859
+ return { deleted: true, entry_id: args.entryId };
40860
+ }
40861
+ const patch = args.action === "archive" ? { status: "archived" } : {
40862
+ ...args.question !== void 0 ? { question: args.question } : {},
40863
+ ...args.answer !== void 0 ? { answer: args.answer } : {},
40864
+ ...args.altQuestions !== void 0 ? { alt_questions: args.altQuestions } : {},
40865
+ ...args.category !== void 0 ? { category: args.category } : {},
40866
+ ...args.sourceUrl !== void 0 ? { source_url: args.sourceUrl } : {},
40867
+ ...args.reviewBy !== void 0 ? { review_by: args.reviewBy } : {},
40868
+ ...args.status !== void 0 ? { status: args.status } : {}
40869
+ };
40870
+ if (!Object.keys(patch).length) return { error: "invalid_request", note: "update needs at least one of question, answer, altQuestions, category, sourceUrl, reviewBy, status" };
40871
+ return await hubPatch(config3, entryPath, { ...patch, ...args.confirm ? { confirm: args.confirm } : {} });
40872
+ } catch (err) {
40873
+ if (err instanceof HubApiError) {
40874
+ const body = bodyOf(err);
40875
+ const code = typeof body?.error === "string" ? body.error : "";
40876
+ const message = typeof body?.message === "string" ? body.message : void 0;
40877
+ if (err.status === 409 && code === "confirm_required") {
40878
+ return {
40879
+ confirm_required: true,
40880
+ confirm_with: body?.confirm_with,
40881
+ note: `${message ?? "This set is public."} Nothing was written. Ask the owner, in plain words, whether to make these answers live on their website assistant; only on a clear yes, repeat the call with confirm set to the account slug.`
40882
+ };
40883
+ }
40884
+ if (err.status === 409 && code === "quota_exceeded") return { error: "quota_exceeded", note: message ?? "This workspace is at its FAQ entry limit." };
40885
+ if (err.status === 503 && code === "faq_unavailable") {
40886
+ return { error: "faq_unavailable", note: "FAQ sets are not live on this account yet. Use the Markdown file method in the awesomate-knowledge skill (references/faqs.md) instead." };
40887
+ }
40888
+ if (err.status === 404 && code === "not_found") return { error: "not_found", note: message ?? "No such FAQ set or entry in this workspace." };
40889
+ if (err.status === 400) return { error: "invalid_request", note: message ?? "The hub refused the request as malformed." };
40890
+ }
40891
+ return mapKnowledgeError(err, config3.apiBase);
40892
+ }
40893
+ }
40793
40894
 
40794
40895
  // src/index.ts
40795
40896
  var config2 = null;
@@ -41008,6 +41109,7 @@ var TITLES = {
41008
41109
  awesomate_knowledge_agent: "Configure knowledge agent",
41009
41110
  awesomate_knowledge_agents: "Manage knowledge agents",
41010
41111
  awesomate_knowledge_collections: "Manage knowledge collections",
41112
+ awesomate_knowledge_faq: "Manage FAQs",
41011
41113
  awesomate_knowledge_people: "Manage knowledge entities",
41012
41114
  awesomate_knowledge_data: "Query knowledge data warehouse"
41013
41115
  };
@@ -42284,6 +42386,50 @@ server.registerTool(
42284
42386
  }
42285
42387
  }
42286
42388
  );
42389
+ server.registerTool(
42390
+ "awesomate_knowledge_faq",
42391
+ {
42392
+ annotations: annotate("awesomate_knowledge_faq", MUTATING),
42393
+ description: "The business's FAQs as a first-class part of its Knowledge Base: every entry is one question and its answer, edited one at a time, cited as 'FAQ: <question>'. Use for 'import the FAQs from my website', 'add this to our FAQ', 'fix that answer'. The owner makes ONE decision per import: whether to publish. action 'sets': every FAQ set with draft/published/archived counts. 'create_set' {setTitle, visibility?, collectionIds?}: idempotent by title; creating a public set asks nothing, since a new set holds nothing live. 'import' {setId | setTitle, entries[{question, answer, altQuestions?, category?, sourceUrl?, externalKey?}] (max 200 per call; call again for more), status?}: upserts on the question itself, so re-running an import UPDATES in place and never duplicates. Default status draft: nothing goes live. Copy answers WORD FOR WORD from the source; never rewrite, shorten or merge them. 'publish' {setId, entryIds?}: every draft (or the listed ones) goes live. 'list' {setId, status?, q?, cursor?, limit?}. 'update' {entryId, question?|answer?|altQuestions?|category?|sourceUrl?|reviewBy?|status?}. 'archive' {entryId}: takes it out of every answer. 'delete' {entryId}. PUBLIC sets: anything that would make an answer live to customers (publish, import as published, editing a published answer) answers confirm_required until you pass confirm: the account slug. Ask the owner in plain words first; never pass the slug on your own initiative. After publishing, answers are searchable within a minute or two; prove it with awesomate_knowledge_ask using a reworded question. faq_unavailable means this account does not have FAQ sets yet: fall back to the Markdown method in the awesomate-knowledge skill.",
42394
+ inputSchema: {
42395
+ action: external_exports.enum(["sets", "create_set", "list", "import", "publish", "update", "archive", "delete"]),
42396
+ setId: external_exports.string().max(200).optional().describe("the set_id from sets or an import result"),
42397
+ setTitle: external_exports.string().min(1).max(200).optional().describe("create_set, or import without a setId: the set is created or reused by this name"),
42398
+ visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("create_set / import-with-setTitle, NEW sets only: 'public' for a website assistant"),
42399
+ collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("create_set: put the set in these collections"),
42400
+ entries: external_exports.array(
42401
+ external_exports.object({
42402
+ question: external_exports.string().min(1).max(500),
42403
+ answer: external_exports.string().min(1).max(4e3),
42404
+ altQuestions: external_exports.array(external_exports.string().max(500)).max(20).optional().describe("other ways customers ask it"),
42405
+ category: external_exports.string().max(80).optional(),
42406
+ sourceUrl: external_exports.string().max(2048).optional().describe("the page the pair was copied from"),
42407
+ externalKey: external_exports.string().max(128).optional().describe("only when the source has its own stable id; the question is the default key")
42408
+ })
42409
+ ).max(200).optional().describe("import: the pairs, word for word"),
42410
+ status: external_exports.enum(["draft", "published", "archived"]).optional().describe("import: draft (default) or published; list: filter; update: set it"),
42411
+ q: external_exports.string().max(200).optional().describe("list: substring over question, answer and alternates"),
42412
+ cursor: external_exports.string().max(20).optional().describe("list: next_cursor from the previous page"),
42413
+ limit: external_exports.number().int().min(1).max(500).optional().describe("list: page size (default 100)"),
42414
+ entryId: external_exports.string().max(64).optional().describe("update/archive/delete: entry_id from list"),
42415
+ entryIds: external_exports.array(external_exports.string().max(64)).max(1e4).optional().describe("publish: only these drafts"),
42416
+ question: external_exports.string().min(1).max(500).optional().describe("update"),
42417
+ answer: external_exports.string().min(1).max(4e3).optional().describe("update"),
42418
+ altQuestions: external_exports.array(external_exports.string().max(500)).max(20).optional().describe("update: REPLACES the list"),
42419
+ category: external_exports.string().max(80).optional().describe("update"),
42420
+ sourceUrl: external_exports.string().max(2048).optional().describe("update"),
42421
+ reviewBy: external_exports.string().max(10).optional().describe("update: YYYY-MM-DD, a date to check this answer is still right"),
42422
+ confirm: external_exports.string().max(64).optional().describe("the account slug, ONLY after the owner said yes to making answers live on a public set")
42423
+ }
42424
+ },
42425
+ async (args) => {
42426
+ try {
42427
+ return knowledgeResult(await knowledgeFaq(requireConfig(), args));
42428
+ } catch (err) {
42429
+ return knowledgeError(err);
42430
+ }
42431
+ }
42432
+ );
42287
42433
  server.registerTool(
42288
42434
  "awesomate_knowledge_people",
42289
42435
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,5 +1,13 @@
1
1
  {
2
2
  "versions": [
3
+ {
4
+ "version": "0.35.0",
5
+ "highlights": [
6
+ "Your FAQs now live as real FAQ entries: Claude imports them as drafts, asks you one question (publish?), and later fixes a single answer without re-uploading anything (awesomate_knowledge_faq)",
7
+ "Every answer your website assistant gives from an FAQ cites the exact question it came from",
8
+ "Nothing goes live on a public assistant without your yes: publishing or editing a live public answer always asks first"
9
+ ]
10
+ },
3
11
  {
4
12
  "version": "0.34.0",
5
13
  "highlights": [
@@ -60,6 +60,7 @@ platform's answer beats anything you remember.
60
60
  | Enable the knowledge base (idempotent; 202 = keep polling status) | `awesomate_knowledge_provision` |
61
61
  | The LIBRARY: search by title/tag/collection/visibility, list, add url·sitemap (with visibility, tags, collections), tag, set visibility (public needs the slug typed), move between collections, remove, ingest jobs | `awesomate_knowledge_sources` |
62
62
  | Collections — named sets of sources a chatbot is scoped to: list / create / update / delete / add / remove members | `awesomate_knowledge_collections` |
63
+ | FAQs: sets of question-and-answer entries, imported as drafts, published with ONE owner yes, edited one answer at a time ([faqs.md](references/faqs.md)) | `awesomate_knowledge_faq` |
63
64
  | Keep a File Manager folder (their n8n file system) synced into the knowledge base: `sync_rules` / `sync_rule_create` / `sync_rule_delete` / `sync_run` / `sync_state` on the sources tool; one file at a time via `awesomate_knowledge_upload {fromFilesPath}` | `awesomate_knowledge_sources`, `awesomate_knowledge_upload` |
64
65
  | Instant search with facet counts (find WHAT is in there: moments, pages, datasets; free) | `awesomate_knowledge_search` |
65
66
  | Ask a question → verified answer + numbered sources (accepts the same facet `filters` to ask within a slice) | `awesomate_knowledge_ask` |
@@ -97,9 +97,29 @@ one citable section per question, and needs nobody to pick the right option.
97
97
  Add the new `##` entries to the same file and upload it with the same title. If the owner
98
98
  has no file yet, start one.
99
99
 
100
- ## Coming soon
101
-
102
- A dedicated FAQ type in the hub is being built. It will let owners edit one answer at a time,
103
- import from any page with one button, and see the questions their assistant could not
104
- answer. When an `awesomate_knowledge_faq` tool appears in your tools, use it instead of the
105
- file method here.
100
+ ## With the FAQ tool (use this first)
101
+
102
+ `awesomate_knowledge_faq` stores every question and answer as its own entry, so an owner can
103
+ fix one answer without re-uploading a file, and every citation names the exact entry. Use it
104
+ whenever it is in your tools. The Markdown method above is the fallback, for when the tool
105
+ answers `faq_unavailable`.
106
+
107
+ 1. Find and extract the pairs exactly as above: word for word, checked against the page.
108
+ 2. `import {setTitle: "<Business> FAQ", visibility: "public", entries: [...]}` in batches of
109
+ 200. Entries land as **drafts**, so nothing is live yet. Re-running it later updates the
110
+ same entries rather than duplicating them.
111
+ 3. Show the count, a few samples and anything you dropped, then ask the one question:
112
+ *"Publish these 42 answers to your website assistant?"*
113
+ 4. On yes, `publish {setId}`. A public set answers `confirm_required` the first time; repeat
114
+ it with `confirm` set to the account slug, which is only allowed because the owner just
115
+ said yes. Never pass the slug on your own initiative.
116
+ On no, first `awesomate_knowledge_sources {action:'set_visibility', sourceId: setId,
117
+ visibility:'internal'}` (lowering visibility needs no confirm), then `publish {setId}`:
118
+ the team's assistants and the owner's own use the answers, the website assistant does
119
+ not. Leaving them as drafts would hide them from everyone.
120
+ 5. Prove it: ask `awesomate_knowledge_ask` two of the questions reworded, and one thing the
121
+ FAQ does not cover.
122
+
123
+ Fixing one answer later is `list {setId, q: "<words from the question>"}` to find it, then
124
+ `update {entryId, answer}` (a live public answer needs the owner's yes again). `archive` takes
125
+ an entry out of every answer without deleting it.