@awesomate/hosting-mcp 0.20.8 → 0.21.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
@@ -40196,6 +40196,49 @@ async function knowledgeSources(config3, args) {
40196
40196
  const page = await hubGet(config3, `/api/knowledge/sources${qs ? `?${qs}` : ""}`);
40197
40197
  return describeSourcePage(page);
40198
40198
  }
40199
+ if (args.action === "search") {
40200
+ const q = new URLSearchParams();
40201
+ if (args.q) q.set("q", args.q);
40202
+ if (args.kind) q.set("kind", args.kind);
40203
+ if (args.visibility) q.set("visibility", args.visibility);
40204
+ for (const t of args.tags ?? []) q.append("tag", t);
40205
+ for (const c of args.collectionIds ?? []) q.append("collection", c);
40206
+ if (args.cursor) q.set("cursor", args.cursor);
40207
+ if (args.limit !== void 0) q.set("limit", String(args.limit));
40208
+ const qs = q.toString();
40209
+ const page = await hubGet(config3, `/api/knowledge/library${qs ? `?${qs}` : ""}`);
40210
+ return { ...describeSourcePage(page), note_visibility: "Each source carries visibility (private|internal|public \u2014 who may retrieve it; enforced) and collections. Nothing marked public = a public chatbot answers nothing." };
40211
+ }
40212
+ if (args.action === "tag") {
40213
+ if (!args.sourceId || !Array.isArray(args.tags)) return { error: "invalid_request", note: "tag needs sourceId and tags (the full replacement list of free-form tags; collections are unaffected)" };
40214
+ return await hubPatch(config3, `/api/knowledge/sources/${encodeURIComponent(args.sourceId)}`, { tags: args.tags });
40215
+ }
40216
+ if (args.action === "set_visibility") {
40217
+ const ids = args.sourceIds?.length ? args.sourceIds : args.sourceId ? [args.sourceId] : [];
40218
+ if (!ids.length || !args.visibility) return { error: "invalid_request", note: "set_visibility needs sourceId (or sourceIds) and visibility" };
40219
+ if (args.visibility === "public" && !args.confirm) {
40220
+ return {
40221
+ error: "confirmation_required",
40222
+ note: `Making ${ids.length} source(s) PUBLIC means anyone with a public key or a public chatbot can retrieve them, and once fetched or crawled that cannot be taken back. Explain this to the user, then call again with confirm set to their account slug (awesomate_whoami \u2192 account) only after they have agreed in so many words.`
40223
+ };
40224
+ }
40225
+ return await hubPost(config3, "/api/knowledge/sources/batch", {
40226
+ source_ids: ids,
40227
+ visibility: args.visibility,
40228
+ ...args.confirm ? { confirm: args.confirm } : {}
40229
+ });
40230
+ }
40231
+ if (args.action === "move") {
40232
+ const ids = args.sourceIds?.length ? args.sourceIds : args.sourceId ? [args.sourceId] : [];
40233
+ if (!ids.length || !args.collectionIds?.length && !args.removeCollectionIds?.length) {
40234
+ return { error: "invalid_request", note: "move needs sourceId (or sourceIds) and collectionIds (add to) and/or removeCollectionIds" };
40235
+ }
40236
+ return await hubPost(config3, "/api/knowledge/sources/batch", {
40237
+ source_ids: ids,
40238
+ ...args.collectionIds?.length ? { add_collection_ids: args.collectionIds } : {},
40239
+ ...args.removeCollectionIds?.length ? { remove_collection_ids: args.removeCollectionIds } : {}
40240
+ });
40241
+ }
40199
40242
  if (args.action === "summary") {
40200
40243
  try {
40201
40244
  const s = await hubGet(config3, "/api/knowledge/sources/summary");
@@ -40232,7 +40275,12 @@ async function knowledgeSources(config3, args) {
40232
40275
  note: "add needs url or sitemap. For a local FILE on this machine, use awesomate_knowledge_upload \u2014 it reads the file off disk and streams it, so the bytes never pass through the conversation."
40233
40276
  };
40234
40277
  }
40235
- const body = args.url ? { url: args.url } : { sitemap: args.sitemap, ...args.since ? { since: args.since } : {} };
40278
+ const body = {
40279
+ ...args.url ? { url: args.url } : { sitemap: args.sitemap, ...args.since ? { since: args.since } : {} },
40280
+ ...args.visibility ? { visibility: args.visibility } : {},
40281
+ ...args.tags?.length ? { tags: args.tags } : {},
40282
+ ...args.collectionIds?.length ? { collection_ids: args.collectionIds } : {}
40283
+ };
40236
40284
  return await hubPost(config3, "/api/knowledge/sources", body);
40237
40285
  } catch (err) {
40238
40286
  return mapKnowledgeError(err, config3.apiBase);
@@ -40593,6 +40641,48 @@ async function knowledgeAgents(config3, args) {
40593
40641
  return mapAgentsError(err, config3.apiBase);
40594
40642
  }
40595
40643
  }
40644
+ async function knowledgeCollections(config3, args) {
40645
+ try {
40646
+ if (args.action === "list") {
40647
+ const r = await hubGet(config3, "/api/knowledge/collections");
40648
+ return { ...r, note: 'A collection is a named set of sources; scope an agent to one with scope.tags ["collection:<collection_id>"]. default_visibility only pre-fills new sources \u2014 each source keeps its own visibility.' };
40649
+ }
40650
+ if (args.action === "create") {
40651
+ if (!args.slug || !args.name) return { error: "invalid_request", note: "create needs slug (lower-case, hyphens) and name" };
40652
+ return await hubPost(config3, "/api/knowledge/collections", {
40653
+ slug: args.slug,
40654
+ name: args.name,
40655
+ ...args.description ? { description: args.description } : {},
40656
+ ...args.defaultVisibility ? { default_visibility: args.defaultVisibility } : {}
40657
+ });
40658
+ }
40659
+ if (!args.collectionId) return { error: "invalid_request", note: `${args.action} needs collectionId (from list)` };
40660
+ if (args.action === "get") {
40661
+ const r = await hubGet(config3, "/api/knowledge/collections");
40662
+ const c = r.collections?.find((x) => x.collection_id === args.collectionId);
40663
+ return c ? { collection: c } : { error: "not_found", note: "No collection with that id in this workspace" };
40664
+ }
40665
+ if (args.action === "update") {
40666
+ const body = {
40667
+ ...args.slug ? { slug: args.slug } : {},
40668
+ ...args.name ? { name: args.name } : {},
40669
+ ...args.description !== void 0 ? { description: args.description } : {},
40670
+ ...args.defaultVisibility ? { default_visibility: args.defaultVisibility } : {}
40671
+ };
40672
+ if (!Object.keys(body).length) return { error: "invalid_request", note: "update needs at least one of slug, name, description, defaultVisibility" };
40673
+ return await hubPatch(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`, body);
40674
+ }
40675
+ if (args.action === "delete") {
40676
+ return await hubDelete(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}`);
40677
+ }
40678
+ if (!args.sourceIds?.length) return { error: "invalid_request", note: `${args.action} needs sourceIds` };
40679
+ return await hubPost(config3, `/api/knowledge/collections/${encodeURIComponent(args.collectionId)}/sources`, {
40680
+ [args.action]: args.sourceIds
40681
+ });
40682
+ } catch (err) {
40683
+ return mapKnowledgeError(err, config3.apiBase);
40684
+ }
40685
+ }
40596
40686
 
40597
40687
  // src/index.ts
40598
40688
  var config2 = null;
@@ -41546,9 +41636,17 @@ server.registerTool(
41546
41636
  server.registerTool(
41547
41637
  "awesomate_knowledge_sources",
41548
41638
  {
41549
- description: "The knowledge base's content sources. action 'list' \u2014 ONE page of sources, most recently ingested first (default 50, max 200 via limit): read `page.has_more`/`next_cursor` and pass cursor to continue \u2014 a page is never the whole library. 'summary' \u2014 exact whole-library counts {total, by_kind, chunks, indexed_chunks, failed_sources, failed_jobs_7d}: use THIS to say what the knowledge base contains, and if failed_jobs_7d > 0 say so \u2014 those ingests are missing from every other count. 'jobs' \u2014 ingest job statuses (optional status filter: queued|running|succeeded|failed). 'add' \u2014 ingest a public page {url} or a whole site {sitemap, since?}; ALWAYS get explicit approval first (ingest costs money and counts against quota), for a local FILE on the user's machine use awesomate_knowledge_upload instead (it streams the file from disk; this tool takes URLs only). A pack_required response means the allowance is exhausted: NOTHING was purchased \u2014 present the pack price (1 credit = $100) and let the user buy from the hub if they want it. 'remove' {sourceId} \u2014 deletes the source AND its indexed content; explicit approval required.",
41639
+ description: "The knowledge base's content sources \u2014 the LIBRARY. action 'search' {q?, kind?, visibility?, tags?, collectionIds?, cursor?, limit?} \u2014 find sources by title/tag substring and metadata filters (instant, free; for CONTENT search use awesomate_knowledge_search); each row carries visibility (private|internal|public \u2014 who may retrieve it, ENFORCED), tags and collections. 'tag' {sourceId, tags} \u2014 REPLACE a source's free-form tags. 'set_visibility' {sourceId|sourceIds, visibility, confirm?} \u2014 change who may retrieve the source(s); 'public' is irreversible once fetched, so it needs the user's explicit agreement and their account slug as confirm. 'move' {sourceId|sourceIds, collectionIds?, removeCollectionIds?} \u2014 add to / remove from collections. 'list' \u2014 ONE page of sources, most recently ingested first (default 50, max 200 via limit): read `page.has_more`/`next_cursor` and pass cursor to continue \u2014 a page is never the whole library. 'summary' \u2014 exact whole-library counts {total, by_kind, chunks, indexed_chunks, failed_sources, failed_jobs_7d}: use THIS to say what the knowledge base contains, and if failed_jobs_7d > 0 say so \u2014 those ingests are missing from every other count. 'jobs' \u2014 ingest job statuses (optional status filter: queued|running|succeeded|failed). 'add' \u2014 ingest a public page {url} or a whole site {sitemap, since?}, optionally with visibility (default private), tags and collectionIds; ALWAYS get explicit approval first (ingest costs money and counts against quota), for a local FILE on the user's machine use awesomate_knowledge_upload instead (it streams the file from disk; this tool takes URLs only). A pack_required response means the allowance is exhausted: NOTHING was purchased \u2014 present the pack price (1 credit = $100) and let the user buy from the hub if they want it. 'remove' {sourceId} \u2014 deletes the source AND its indexed content; explicit approval required.",
41550
41640
  inputSchema: {
41551
- action: external_exports.enum(["list", "summary", "add", "remove", "jobs"]),
41641
+ action: external_exports.enum(["list", "summary", "add", "remove", "jobs", "search", "tag", "set_visibility", "move"]),
41642
+ q: external_exports.string().max(100).optional().describe("search: title/tag substring"),
41643
+ kind: external_exports.string().max(32).optional().describe("search: source kind filter (web, document, video, audio, image, book, dataset)"),
41644
+ visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("add: the audience the new source is cleared for (default private); set_visibility: the level to apply; search: filter"),
41645
+ tags: external_exports.array(external_exports.string().max(64)).max(64).optional().describe("add: free-form tags; tag: the REPLACEMENT list; search: filter (AND)"),
41646
+ collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("add: put the source in these collections; move: add to; search: filter"),
41647
+ removeCollectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("move: remove from these collections"),
41648
+ sourceIds: external_exports.array(external_exports.string().max(300)).max(200).optional().describe("set_visibility/move: several sources at once"),
41649
+ confirm: external_exports.string().max(64).optional().describe("set_visibility to 'public' only: the account slug, after the user agreed in so many words"),
41552
41650
  url: external_exports.string().url().optional().describe("add: one public page / blog post / YouTube link"),
41553
41651
  sitemap: external_exports.string().url().optional().describe("add: sitemap.xml URL \u2014 ingests every listed page"),
41554
41652
  since: external_exports.string().optional().describe("add+sitemap only: skip entries with lastmod older than this ISO date"),
@@ -41558,10 +41656,10 @@ server.registerTool(
41558
41656
  limit: external_exports.number().int().min(1).max(200).optional().describe("list only: page size (default 50, max 200)")
41559
41657
  }
41560
41658
  },
41561
- async ({ action, url, sitemap, since, sourceId, status, cursor, limit }) => {
41659
+ async ({ action, url, sitemap, since, sourceId, status, cursor, limit, q, kind, visibility, tags, collectionIds, removeCollectionIds, sourceIds, confirm }) => {
41562
41660
  try {
41563
41661
  return knowledgeResult(
41564
- await knowledgeSources(requireConfig(), { action, url, sitemap, since, sourceId, status, cursor, limit })
41662
+ await knowledgeSources(requireConfig(), { action, url, sitemap, since, sourceId, status, cursor, limit, q, kind, visibility, tags, collectionIds, removeCollectionIds, sourceIds, confirm })
41565
41663
  );
41566
41664
  } catch (err) {
41567
41665
  return knowledgeError(err);
@@ -41672,6 +41770,28 @@ server.registerTool(
41672
41770
  }
41673
41771
  }
41674
41772
  );
41773
+ server.registerTool(
41774
+ "awesomate_knowledge_collections",
41775
+ {
41776
+ description: "Collections: named sets of knowledge sources with a default audience \u2014 the unit a chatbot is scoped to (agent scope.tags ['collection:<collection_id>']). action 'list' \u2014 every collection with source counts by visibility. 'create' {slug, name, description?, defaultVisibility?} \u2014 defaultVisibility only pre-fills NEW sources' visibility; each source keeps its own. 'get' {collectionId}. 'update' {collectionId, slug?|name?|description?|defaultVisibility?}. 'delete' {collectionId} \u2014 removes the collection; the sources survive (explicit approval first). 'add'/'remove' {collectionId, sourceIds} \u2014 membership. A source may be in several collections. A PUBLIC chatbot over a collection whose sources are all private answers nothing \u2014 check visibility (awesomate_knowledge_sources search) before building on one.",
41777
+ inputSchema: {
41778
+ action: external_exports.enum(["list", "create", "get", "update", "delete", "add", "remove"]),
41779
+ collectionId: external_exports.string().max(64).optional().describe("get/update/delete/add/remove: collection_id from list"),
41780
+ slug: external_exports.string().max(64).optional().describe("create/update: lower-case letters, digits, hyphens"),
41781
+ name: external_exports.string().max(120).optional().describe("create/update"),
41782
+ description: external_exports.string().max(2e3).optional().describe("create/update"),
41783
+ defaultVisibility: external_exports.enum(["private", "internal", "public"]).optional().describe("create/update: pre-fills new sources only"),
41784
+ sourceIds: external_exports.array(external_exports.string().max(300)).max(200).optional().describe("add/remove")
41785
+ }
41786
+ },
41787
+ async (args) => {
41788
+ try {
41789
+ return knowledgeResult(await knowledgeCollections(requireConfig(), args));
41790
+ } catch (err) {
41791
+ return knowledgeError(err);
41792
+ }
41793
+ }
41794
+ );
41675
41795
  server.registerTool(
41676
41796
  "awesomate_knowledge_people",
41677
41797
  {
@@ -41978,10 +42098,13 @@ server.registerTool(
41978
42098
  description: "Ingest ONE file from the user's own computer into their Knowledge Base (Pro+). Pass a LOCAL PATH \u2014 this server runs on their machine and streams the file to the hub itself, so the file contents never pass through the conversation. Handles documents (pdf, md, txt), business data (csv, tsv, xls, xlsx, json), audio and video (transcribed), and images. Word/PowerPoint/RTF/EPUB files are NOT parseable yet \u2014 the tool refuses them with the workaround (export to PDF, or save as .md/.txt). Max 100 MB per file; bigger media goes through the hub's Knowledge \u2192 Sources page. INGESTING COSTS MONEY and counts against the monthly allowance, so ALWAYS get explicit approval for the specific file(s) first and say what it will consume. For several files, call once per file and report progress \u2014 do not loop silently. Returns a job with its live status; poll awesomate_knowledge_sources {action:'jobs'} until it succeeds (a duplicate upload replays the earlier job \u2014 a response already reading succeeded needs no polling), then probe the content with awesomate_knowledge_ask before building anything on it. A pack_required response means the allowance is exhausted: nothing was ingested and nothing was purchased.",
41979
42099
  inputSchema: {
41980
42100
  path: external_exports.string().min(1).max(4096).describe("Path to the file on the user's machine (~ is expanded)"),
41981
- title: external_exports.string().max(300).optional().describe("Display title; defaults to the filename")
42101
+ title: external_exports.string().max(300).optional().describe("Display title; defaults to the filename"),
42102
+ visibility: external_exports.enum(["private", "internal", "public"]).optional().describe("Who may retrieve this source (default private). public = customers and public chatbots; irreversible once fetched \u2014 ask first."),
42103
+ tags: external_exports.array(external_exports.string().max(64)).max(64).optional().describe("Free-form tags for the new source"),
42104
+ collectionIds: external_exports.array(external_exports.string().max(64)).max(16).optional().describe("Put the new source in these collections (awesomate_knowledge_collections list)")
41982
42105
  }
41983
42106
  },
41984
- async ({ path: rawPath, title }) => {
42107
+ async ({ path: rawPath, title, visibility, tags, collectionIds }) => {
41985
42108
  try {
41986
42109
  const { statSync, existsSync: existsSync3 } = await import("node:fs");
41987
42110
  const { resolve: resolve2, basename, extname } = await import("node:path");
@@ -42021,7 +42144,12 @@ server.registerTool(
42021
42144
  }
42022
42145
  const contentType = KB_UPLOAD_TYPES[ext] ?? "application/octet-stream";
42023
42146
  const filename = title?.trim() ? `${title.trim()}${ext}` : basename(abs);
42024
- const result = await hubUploadFile(requireConfig(), "/api/knowledge/sources", {
42147
+ const uploadQuery = new URLSearchParams();
42148
+ if (visibility) uploadQuery.set("visibility", visibility);
42149
+ for (const t of tags ?? []) uploadQuery.append("tags", t);
42150
+ for (const c of collectionIds ?? []) uploadQuery.append("collection_ids", c);
42151
+ const uploadQs = uploadQuery.toString();
42152
+ const result = await hubUploadFile(requireConfig(), `/api/knowledge/sources${uploadQs ? `?${uploadQs}` : ""}`, {
42025
42153
  localPath: abs,
42026
42154
  filename,
42027
42155
  contentType
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.20.8",
3
+ "version": "0.21.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",
@@ -58,7 +58,8 @@ platform's answer beats anything you remember.
58
58
  |---|---|
59
59
  | Tenant state, plan gate, consent, quota, packs (CALL FIRST) | `awesomate_knowledge_status` |
60
60
  | Enable the knowledge base (idempotent; 202 = keep polling status) | `awesomate_knowledge_provision` |
61
- | Sources list / add url·sitemap / remove / ingest jobs | `awesomate_knowledge_sources` |
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
+ | Collections — named sets of sources a chatbot is scoped to: list / create / update / delete / add / remove members | `awesomate_knowledge_collections` |
62
63
  | Instant search with facet counts (find WHAT is in there: moments, pages, datasets; free) | `awesomate_knowledge_search` |
63
64
  | Ask a question → verified answer + numbered sources (accepts the same facet `filters` to ask within a slice) | `awesomate_knowledge_ask` |
64
65
  | Agent persona / fallback message / model tier — get & set (the single workspace default) | `awesomate_knowledge_agent` |