brilliant-directories-mcp 6.41.76 → 6.41.78

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.
@@ -1858,14 +1858,14 @@
1858
1858
  }
1859
1859
  }
1860
1860
  },
1861
- "description": "Create a member. Writes live data. Welcome email silent by default - set `send_email_notifications=1` to trigger.\n\n**Required:** `email`, `password`, `subscription_id`.\n\n**Use when:** adding members outside BD signup - CSV imports, scraped listings, Zapier automations, admin test accounts.\n\n**Enums:** `active`: `1`=Not Active, `2`=Active, `3`=Canceled, `4`=On Hold, `5`=Past Due, `6`=Incomplete. `listing_type`: `Individual`, `Company`. `verified`/`nationwide`: `1`/`0`.\n\n**Prerequisites:** `subscription_id` MUST reference an existing plan - discover via `listMembershipPlans`. Categories auto-create — see the **Category taxonomy** section of this same tool.\n\n**Parameter interactions:**\n\n- `auto_image_import=1` - fetch external image URLs into BD storage (for `profile_photo`, `logo`, `cover_photo` holding URLs). Without it, BD stores the URL as-is; images break if source host goes down. Supports JPG/PNG/GIF/WebP/SVG. Processing delay: several minutes. **Recommended default for any external image URL.**\n\n- `auto_geocode=1` - requires \"Pretty URLs with Google Maps\" site feature enabled.\n\n- `send_email_notifications=1` - trigger welcome email per plan settings.\n\n**Category taxonomy:**\n\n- Top-level: pick ONE of `profession_id` (numeric, must exist OR will auto-create) or `profession_name` (string, auto-creates if missing).\n\n- `services` (sub-categories) supports 4 formats:\n - Single: `\"PVC repair\"` or `\"1823\"`\n - Multiple: `\"PVC repair,Water heater\"` or `\"1823,1824\"`\n - Sub-sub via `=>`: `\"Honda=>2022\"` (creates sub \"Honda\" + sub-sub \"2022\" under Honda)\n - Mixed: `\"Honda=>2022,Honda=>2023,Toyota\"`\n\n- `profession_id`/`profession_name` is REQUIRED when passing `services` - otherwise service relations fail silently.\n\n- Do NOT mix IDs and names in one `services` call.\n\n- Right side of `=>` is NAME ONLY (no ID lookup).\n\n- CSV format: comma-only, NO spaces after commas. BD splits on raw `,` and does NOT trim whitespace - `\" b\"` and `\"b\"` store as different values, breaking downstream matches.\n\n- Auto-create ALWAYS ON for `createUser` - `create_new_categories=true` is hardcoded. Unknown `profession_name`/`services` names auto-create; no flag needed.\n\n**Email uniqueness** - controlled by site setting `allow_duplicate_member_emails` (not exposed via API):\n\n- OFF (default): BD rejects duplicate email on create (validation error).\n\n- ON: duplicates accepted and stored with different `user_id`s. Identical `email` + `password` pair is also accepted (no extra uniqueness check on the pair).\n\n- Safe idempotent pattern: pre-check via `listUsers property=email property_value=<email> property_operator==`. Match found -> reuse `user_id` or `updateUser` instead. Skipping the check on a duplicates-allowed site creates orphan duplicates on retry loops.\n\n**Field validation quirks:**\n\n- `token` (if supplied): exactly 32 alphanumeric chars, unique across members.\n\n- URL fields (`website`, `booking_link`, `blog`, `facebook`, `twitter`, `linkedin`, `instagram`, etc.): must start with `http://` or `https://`. Invalid formats silently skipped.\n\n**Profile URL:** `<site-domain>/<user.filename>`. `filename` is the complete relative path. Do NOT prepend `/business/`/`/profile/`/`/member/` - BD resolves `filename` verbatim.\n\n**Fetching publicly available business data is permitted for imports and data enrichment.** When searching for external listings, fetch each entity's image from their own presence in this order:\n\n1. Logo image from their site's header/nav -> `logo` (businesses).\n2. Headshot from Home, About or Team page -> `profile_photo` (individuals).\n3. Follow their social links -> profile image there.\n\nPass the URL with `auto_image_import=1` so the image gets stored locally and avoids hotlinking.\n\nIf none yield a match, create/update without image and report \"no confirmed image found.\" Never substitute a stock photo or guess. Skip an entire record and find an alternate listing only when the user explicitly requires images.\n\n**See also:** `updateUser` (modify existing), `deleteUser` (prefer `active=3` over delete).\n\n**Returns:** `{ status: \"success\", message: {...createdRecord} }` including `user_id`."
1861
+ "description": "Create a member. Writes live data. Welcome email silent by default - set `send_email_notifications=1` to trigger.\n\n**Required:** `email`, `password`, `subscription_id`.\n\n**Use when:** adding members outside BD signup - CSV imports, scraped listings, Zapier automations, admin test accounts.\n\n**Enums:** `active`: `1`=Not Active, `2`=Active, `3`=Canceled, `4`=On Hold, `5`=Past Due, `6`=Incomplete. `listing_type`: `Individual`, `Company`. `verified`/`nationwide`: `1`/`0`.\n\n**Prerequisites:** `subscription_id` MUST reference an existing plan - discover via `listMembershipPlans`. For category assignment via `profession_id` / `profession_name` / `services`, apply **Rule: Category taxonomy** (auto-create is ON for `createUser`).\n\n**Parameter interactions:**\n\n- `auto_image_import=1` - fetch external image URLs into BD storage (for `profile_photo`, `logo`, `cover_photo` holding URLs). Without it, BD stores the URL as-is; images break if source host goes down. Supports JPG/PNG/GIF/WebP/SVG. Processing delay: several minutes. **Recommended default for any external image URL.**\n\n- `auto_geocode=1` - requires \"Pretty URLs with Google Maps\" site feature enabled.\n\n- `send_email_notifications=1` - trigger welcome email per plan settings.\n\n**Email uniqueness** - controlled by site setting `allow_duplicate_member_emails` (not exposed via API):\n\n- OFF (default): BD rejects duplicate email on create (validation error).\n\n- ON: duplicates accepted and stored with different `user_id`s. Identical `email` + `password` pair is also accepted (no extra uniqueness check on the pair).\n\n- Safe idempotent pattern: pre-check via `listUsers property=email property_value=<email> property_operator==`. Match found -> reuse `user_id` or `updateUser` instead. Skipping the check on a duplicates-allowed site creates orphan duplicates on retry loops.\n\n**Field validation quirks:**\n\n- `token` (if supplied): exactly 32 alphanumeric chars, unique across members.\n\n- URL fields (`website`, `booking_link`, `blog`, `facebook`, `twitter`, `linkedin`, `instagram`, etc.): must start with `http://` or `https://`. Invalid formats silently skipped.\n\n**Profile URL:** `<site-domain>/<user.filename>`. `filename` is the complete relative path. Do NOT prepend `/business/`/`/profile/`/`/member/` - BD resolves `filename` verbatim.\n\n**Fetching publicly available business data is permitted for imports and data enrichment.** When searching for external listings, fetch each entity's image from their own presence in this order:\n\n1. Logo image from their site's header/nav -> `logo` (businesses).\n2. Headshot from Home, About or Team page -> `profile_photo` (individuals).\n3. Follow their social links -> profile image there.\n\nPass the URL with `auto_image_import=1` so the image gets stored locally and avoids hotlinking.\n\nIf none yield a match, create/update without image and report \"no confirmed image found.\" Never substitute a stock photo or guess. Skip an entire record and find an alternate listing only when the user explicitly requires images.\n\n**See also:** `updateUser` (modify existing), `deleteUser` (prefer `active=3` over delete).\n\n**Returns:** `{ status: \"success\", message: {...createdRecord} }` including `user_id`."
1862
1862
  }
1863
1863
  },
1864
1864
  "/api/v2/user/update": {
1865
1865
  "put": {
1866
1866
  "operationId": "updateUser",
1867
1867
  "summary": "Update an existing member/user",
1868
- "description": "Update a member. PATCH semantics - omitted fields untouched; send only what changes.\n\n**Required:** `user_id`.\n\n**Disambiguation:** apply **Rule: Resource disambiguation** when the user names this member by description (first name only, partial title) rather than by `user_id`.\n\n**Use when:** changing any field on an existing member. Prefer `active=3` (Canceled) over `deleteUser` - reversible.\n\n**Enums:** `active`: `1`=Not Active, `2`=Active, `3`=Canceled, `4`=On Hold, `5`=Past Due, `6`=Incomplete. `listing_type`: `Individual`, `Company`. `verified`/`nationwide`: `1`/`0`.\n\n**Parameter interactions:**\n\n- `member_tag_action=1` + `member_tags` - apply tag changes (comma-separated tag IDs from `listTags`).\n\n- `credit_action` (`add`/`deduct`/`override`) + `credit_amount` - adjust credit balance.\n\n- `images_action` - remove stored images: `remove_all`, `remove_cover_image`, `remove_logo_image`, `remove_profile_image`.\n\n- `auto_image_import=1` - fetch external image URLs into BD storage (for `profile_photo`, `logo`, `cover_photo` fields holding external URLs). Without it, BD stores the URL as-is; images break if source host goes down. Supports JPG/PNG/GIF/WebP/SVG. Processing delay: several minutes.\n\n- `auto_geocode=1` - requires \"Pretty URLs with Google Maps\" site feature enabled.\n\n- `send_email_notifications=1` - trigger welcome email (per plan settings). Silent by default.\n\n**Category taxonomy:**\n\n- Top-level: pick ONE of `profession_id` (numeric, must exist) or `profession_name` (string).\n\n- `services` (sub-categories) supports 4 formats:\n - Single: `\"PVC repair\"` or `\"1823\"`\n - Multiple: `\"PVC repair,Water heater\"` or `\"1823,1824\"`\n - Sub-sub via `=>`: `\"Honda=>2022\"` (creates sub \"Honda\" + sub-sub \"2022\")\n - Mixed: `\"Honda=>2022,Honda=>2023,Toyota\"`\n\n- `profession_id`/`profession_name` is REQUIRED when passing `services` - otherwise service relations fail silently.\n\n- Do NOT mix IDs and names in one `services` call - pick one format.\n\n- Right side of `=>` is NAME ONLY (no ID lookup).\n\n- CSV format: comma-only, NO spaces after commas. BD splits on raw `,` and does NOT trim whitespace - `\" b\"` and `\"b\"` store as different values, breaking downstream matches.\n\n- Auto-create OFF by default on update. Pass `create_new_categories=1` to auto-create unknown names.\n\n**Destructive side effect:** changing `profession_id` WIPES existing sub-category relations for the member. To move to a new top category without losing sub-cats, re-send complete `services` list in the same call.\n\n**Email uniqueness** - controlled by site setting `allow_duplicate_member_emails` (not exposed via API):\n\n- OFF (default): BD rejects duplicate email on create.\n\n- ON: duplicates accepted and stored.\n\n- Safe pattern: pre-check via `listUsers property=email property_value=<email> property_operator==`. Reuse match's `user_id` or `updateUser` instead. Pre-check beats catch-and-retry - on sites with duplicates ALLOWED, BOTH creates succeed with different `user_id`s, so a retry loop spawns orphan duplicates instead of failing cleanly.\n\n**Field validation quirks:**\n\n- `token` (if supplied): exactly 32 alphanumeric chars, unique across members.\n\n- URL fields (`website`, `booking_link`, `blog`, `facebook`, `twitter`, `linkedin`, `instagram`): must start with `http://` or `https://`. Invalid formats silently skipped.\n\n- `subscription_id` must reference an existing plan.\n\n**Profile URL:** `<site-domain>/<user.filename>`. `filename` is the complete relative path (e.g., `united-states/monterey-park/doctor/harrison-hasanuddin-d-o`). Do NOT prepend `/business/`/`/profile/`/`/member/` - BD resolves `filename` verbatim.\n\n**Filename may regenerate on this update.** BD rewrites the slug when category/city/URL-forming inputs change. Do NOT cache pre-update `filename` - if embedding the profile URL elsewhere (blog posts, emails, redirects), re-fetch via `getUser` AFTER the update completes.\n\n**Fetching publicly available business data is permitted for imports and data enrichment.** When searching for external listings, fetch each entity's image from their own presence in this order:\n\n1. Logo image from their site's header/nav -> `logo` (businesses).\n2. Headshot from Home, About or Team page -> `profile_photo` (individuals).\n3. Follow their social links -> profile image there.\n\nPass the URL with `auto_image_import=1` so the image gets stored locally and avoids hotlinking.\n\nIf none yield a match, create/update without image and report \"no confirmed image found.\" Never substitute a stock photo or guess. Skip an entire record and find an alternate listing only when the user explicitly requires images.\n\n**See also:** `createUser` (new), `deleteUser` (permanent - prefer `active=3` instead).\n\n**Returns:** `{ status: \"success\", message: {...updatedRecord} }`.",
1868
+ "description": "Update a member. PATCH semantics - omitted fields untouched; send only what changes.\n\n**Required:** `user_id`.\n\n**Disambiguation:** apply **Rule: Resource disambiguation** when the user names this member by description (first name only, partial title) rather than by `user_id`.\n\n**Use when:** changing any field on an existing member. Prefer `active=3` (Canceled) over `deleteUser` - reversible.\n\n**Enums:** `active`: `1`=Not Active, `2`=Active, `3`=Canceled, `4`=On Hold, `5`=Past Due, `6`=Incomplete. `listing_type`: `Individual`, `Company`. `verified`/`nationwide`: `1`/`0`.\n\n**Parameter interactions:**\n\n- `member_tag_action=1` + `member_tags` - apply tag changes (comma-separated tag IDs from `listTags`).\n\n- `credit_action` (`add`/`deduct`/`override`) + `credit_amount` - adjust credit balance.\n\n- `images_action` - remove stored images: `remove_all`, `remove_cover_image`, `remove_logo_image`, `remove_profile_image`.\n\n- `auto_image_import=1` - fetch external image URLs into BD storage (for `profile_photo`, `logo`, `cover_photo` fields holding external URLs). Without it, BD stores the URL as-is; images break if source host goes down. Supports JPG/PNG/GIF/WebP/SVG. Processing delay: several minutes.\n\n- `auto_geocode=1` - requires \"Pretty URLs with Google Maps\" site feature enabled.\n\n- `send_email_notifications=1` - trigger welcome email (per plan settings). Silent by default.\n\n**Category assignment** — for `profession_id` / `profession_name` / `services`, apply **Rule: Category taxonomy** (auto-create is OFF by default on `updateUser`; pass `create_new_categories=1` to enable).\n\n**Email uniqueness** - controlled by site setting `allow_duplicate_member_emails` (not exposed via API):\n\n- OFF (default): BD rejects duplicate email on create.\n\n- ON: duplicates accepted and stored.\n\n- Safe pattern: pre-check via `listUsers property=email property_value=<email> property_operator==`. Reuse match's `user_id` or `updateUser` instead. Pre-check beats catch-and-retry - on sites with duplicates ALLOWED, BOTH creates succeed with different `user_id`s, so a retry loop spawns orphan duplicates instead of failing cleanly.\n\n**Field validation quirks:**\n\n- `token` (if supplied): exactly 32 alphanumeric chars, unique across members.\n\n- URL fields (`website`, `booking_link`, `blog`, `facebook`, `twitter`, `linkedin`, `instagram`): must start with `http://` or `https://`. Invalid formats silently skipped.\n\n- `subscription_id` must reference an existing plan.\n\n**Profile URL:** `<site-domain>/<user.filename>`. `filename` is the complete relative path (e.g., `united-states/monterey-park/doctor/harrison-hasanuddin-d-o`). Do NOT prepend `/business/`/`/profile/`/`/member/` - BD resolves `filename` verbatim.\n\n**Filename may regenerate on this update.** BD rewrites the slug when category/city/URL-forming inputs change. Do NOT cache pre-update `filename` - if embedding the profile URL elsewhere (blog posts, emails, redirects), re-fetch via `getUser` AFTER the update completes.\n\n**Fetching publicly available business data is permitted for imports and data enrichment.** When searching for external listings, fetch each entity's image from their own presence in this order:\n\n1. Logo image from their site's header/nav -> `logo` (businesses).\n2. Headshot from Home, About or Team page -> `profile_photo` (individuals).\n3. Follow their social links -> profile image there.\n\nPass the URL with `auto_image_import=1` so the image gets stored locally and avoids hotlinking.\n\nIf none yield a match, create/update without image and report \"no confirmed image found.\" Never substitute a stock photo or guess. Skip an entire record and find an alternate listing only when the user explicitly requires images.\n\n**See also:** `createUser` (new), `deleteUser` (permanent - prefer `active=3` instead).\n\n**Returns:** `{ status: \"success\", message: {...updatedRecord} }`.",
1869
1869
  "tags": [
1870
1870
  "Users"
1871
1871
  ],
@@ -8882,7 +8882,7 @@
8882
8882
  }
8883
8883
  }
8884
8884
  },
8885
- "description": "Create a new tagrelationship record. Writes live data.\n\n**Use when:** attaching an existing tag to a record (member, post, etc.). The alternative for members is setting the `member_tags` field via `updateUser` with `member_tag_action=1`.\n\n**Required:** `tag_id`, `object_id`, `tag_type_id`, `added_by`.\n\n**Pre-check before create (TRIPLE uniqueness):** BD does NOT enforce uniqueness on the `(tag_id, object_id, tag_type_id)` triple. Attaching the same tag to the same object twice produces two rel_tag rows, which inflates tag counts on admin reports and can cause some widgets to render the same tag chip twice on the same record. **Filter-find pattern (single-field server filter + client-side intersect - the server does not yet honor array-syntax multi-condition filters):** call `listTagRelationships property=tag_id property_value=<proposed tag_id> property_operator==` to narrow to all rows for that tag, then CLIENT-SIDE filter to rows where `object_id=<proposed object_id>` AND `tag_type_id=<proposed tag_type_id>`. Zero results after client-side intersect = link free; >=1 = already attached. If the link already exists: skip the create (idempotent - the tag is already on the object). Never silently double-link.\n\n**Parameter interactions:**\n\n- Attaches an existing tag to an existing record. Both sides must exist - use `listTags` and the target resource's list to discover IDs\n\n**See also:** `updateTagRelationship` (modify existing).\n\n**`tag_type_id` + `object_id` - how to target the right record (from BD `tag_types` table):**\n\nThe `tag_type_id` determines WHICH resource/table the `object_id` lives in. Discover mapping via `listTagTypes` - each tag type row has a `table_relation` field naming its target table. Example mapping:\n\n| tag_type_id | type_name | Target table (table_relation) | What object_id references |\n|---|---|---|---|\n| 1 | Users | `users_data` | `user_id` |\n| (other rows) | (other types) | e.g. `data_widgets`, `menus`, `forms` | That table's primary key |\n\n**Process:** call `listTagTypes` first to see the `tag_type_id` -> `table_relation` mapping on your site, then pick the appropriate `tag_type_id` and supply the matching record's PK as `object_id`. Widgets, menus, and forms all support tags via this same mechanism.\n\n"
8885
+ "description": "Create a new tagrelationship record. Writes live data.\n\n**Use when:** attaching an existing tag to a record (member, post, etc.). The alternative for members is setting the `member_tags` field via `updateUser` with `member_tag_action=1`.\n\n**Required:** `tag_id`, `object_id`, `tag_type_id`, `added_by`.\n\n**Pre-check before create (TRIPLE uniqueness):** BD does NOT enforce uniqueness on the `(tag_id, object_id, tag_type_id)` triple. Attaching the same tag to the same object twice produces two rel_tag rows, which inflates tag counts on admin reports and can cause some widgets to render the same tag chip twice on the same record. **Filter-find pattern (single-field server filter + client-side intersect - the server does not yet honor array-syntax multi-condition filters):** call `listTagRelationships property=tag_id property_value=<proposed tag_id> property_operator==` to narrow to all rows for that tag, then CLIENT-SIDE filter to rows where `object_id=<proposed object_id>` AND `tag_type_id=<proposed tag_type_id>`. Zero results after client-side intersect = link free; >=1 = already attached. If the link already exists: skip the create (idempotent - the tag is already on the object). Never silently double-link.\n\n**Parameter interactions:**\n\n- Attaches an existing tag to an existing record. Both sides must exist - use `listTags` and the target resource's list to discover IDs\n\n**See also:** `updateTagRelationship` (modify existing).\n\n**`tag_type_id` + `object_id` - how to target the right record (from BD `tag_types` table):**\n\nThe `tag_type_id` determines WHICH resource/table the `object_id` lives in. Discover mapping via `listTagTypes` - each tag type row has a `table_relation` field naming its target table. Example mapping:\n\n| tag_type_id | type_name | Target table (table_relation) | What object_id references |\n|---|---|---|---|\n| 1 | Users | `users_data` | `user_id` |\n| (other rows) | (other types) | e.g. `data_widgets`, `menus`, `forms` | That table's primary key |\n\n**Process:** call `listTagTypes` first to see the `tag_type_id` -> `table_relation` mapping on your site, then pick the appropriate `tag_type_id` and supply the matching record's PK as `object_id`. Widgets, menus, and forms all support tags via the same `tag_type_id` + `object_id` lookup pattern.\n\n"
8886
8886
  }
8887
8887
  },
8888
8888
  "/api/v2/rel_tags/update": {
@@ -9820,7 +9820,7 @@
9820
9820
  "post": {
9821
9821
  "operationId": "createWebPage",
9822
9822
  "summary": "Create a page",
9823
- "description": "Create a `list_seo` page record. Writes live data.\n\n**Cache refresh is automatic.** Response includes `auto_cache_refreshed: true` after successful writes; no manual `refreshSiteCache` call needed. If `auto_cache_refreshed: false`, check `auto_cache_refresh_error` and retry `refreshSiteCache` once.\n\n**Required fields:** `seo_type`. `filename` is required for every `seo_type` EXCEPT `data_category` (the WRAPPER generates a 10-char lowercase alphanumeric placeholder slug for that type and auto-creates a 301 redirect to the canonical post-type URL; the public URL routes via the post type's `data_filename`, not `list_seo.filename`).\n\n**Filename uniqueness — enforced by the wrapper, no exceptions.** BD does NOT enforce unique `filename` server-side, but duplicates break the platform (two pages at the same URL render non-deterministically). The wrapper auto-pre-checks `listWebPages` for an existing slug before forwarding the create. If a row exists, the create is rejected with the existing `seo_id` so the agent can `updateWebPage` instead, or pick a unique slug. There is no agent-facing bypass; for `seo_type=data_category` the wrapper generates the slug itself (10-char lowercase alphanumeric — statistically unique across `36^10`, no pre-check needed).\n\n**Thin-content warning:** if no `title`, `h1`, `meta_desc`, or `content` is set on a `seo_type=content` create, a `_warning` field is attached to the response. The page is still created and is publicly live — Google may index it as thin content. Fix: provide at least one of those fields on the create call, or `updateWebPage` immediately after, or `deleteWebPage` if the create was premature.\n\n**Asset field routing (mandatory - Froala strips mismatched content silently):**\n\n- `content` - body HTML. No `<style>`/`<script>` tags. Supports `[widget=Name]` shortcodes + `%%%token%%%`.\n\n- `content_css` - raw CSS rules. NO `<style>` wrapper. Scope to a unique page class; never target `.container`/`.froala-table`/`.image-placeholder` (reserved). Do NOT use `@import` (causes FOUC/CLS - use `content_head` `<link>` tag instead).\n\n- `content_footer_html` - JavaScript, pixels, analytics embeds (`<script>` tags OK here). IIFE-wrap + scope.\n\n- `content_head` - head-only deps (`<link>`, `<meta>`, JSON-LD, external stylesheets, fonts).\n\n- `content_footer` - MISLEADING NAME. NOT footer HTML. Page-access gate enum: `\"\"` (public), `\"members_only\"`, `\"digital_products\"`.\n\n- Hero banner -> `enable_hero_section` + `hero_*` + `h1_*`/`h2_*` fields.\n\nAll asset fields accept raw content verbatim. No CDATA, no `<parameter>`/`<invoke>`/`<function_calls>` scaffolding, no entity-escaped HTML — forbidden anywhere in the value, not just as wrappers. Server strips these as a safety net; do not rely on it.\n\n**SVG/canvas prohibited in `content`** - Froala strips them. Charts/diagrams go in a custom Widget, embedded via `[widget=Name]` shortcode.\n\n**`seo_type` values:** `home` (system-seeded; cannot CREATE homepage, only `updateWebPage`), `content` (generic static page), `profile_search_results` (member search override — apply **Rule: Member search SEO pages**), `data_category` (post search), `custom_widget_page`, `password_retrieval_page`, `unsubscribed`.\n\n**Hero section - when `enable_hero_section` = `1` or `2`, apply **Rule: Hero readability bundle** (atomic — all listed values must be sent together).** Notes:\n\n- All color fields RGB ONLY (`rgb(0, 0, 0)`) - hex not accepted.\n\n- Hero `h1_*`/`h2_*` fields style ONLY the hero banner; H1/H2 TEXT comes from the record's top-level `h1`/`h2` fields.\n\n- Hero image: content-relevant Pexels stock photo (free license, no attribution). See **Rule: Image URLs** (imported field — bare URL, no query string). Never `picsum.photos`/`lorempixel`/`placekitten`.\n\n- Hero gap-fix CSS (`seo_type=content` ONLY): add `.hero_section_container + div.clearfix-lg {display:none}` to `content_css` to close BD's 40px clearfix gap. **Never add this rule on any other `seo_type`** - on `profile_search_results` / `data_category` the clearfix provides needed spacing before live search-results; hiding it causes results to butt-join the hero.\n\n- Hero is cache-gated — but `createWebPage`/`updateWebPage` auto-refresh handles it; no separate call needed.\n\n- **Homepage hero is BENIGN**: `seo_id=1` stores hero fields but the homepage template does NOT render them. Skip hero fields on homepage unless user explicitly asks.\n\n**`profile_search_results` SEO pages - thin-content remedy workflow:**\n\nUsed to override BD's auto-generated dynamic search URLs (e.g. `california/beverly-hills/plumbers`) with static custom SEO copy. Creating a `list_seo` row with a matching `filename` takes over the public URL.\n\n**CRITICAL - `filename` MUST be a real slug BD's dynamic router recognizes.** Arbitrary slugs render HTTP 404 publicly even when the record is created successfully. See **Rule: Member search SEO pages** for the canonical slug hierarchy (`country/state/city/top/sub`, strict order, any subset valid) and the live-lookup endpoints for each segment. Wrapper validates segments at runtime — country slug is derived from `country_name` (lowercase + spaces→hyphens). For arbitrary-URL static pages use `seo_type=content`.\n\n**Workflow for \"add SEO to [category] in [location]\":**\n1. Resolve each human name to its slug via the relevant `list*` endpoint (exact-match `=`).\n2. For ambiguous inputs (e.g. \"Beverly Hills plumbers\" - could be `beverly-hills/plumbers` or `california/beverly-hills/plumbers`), ask user which variant.\n3. Pre-check: `listWebPages property=filename property_value=<slug> property_operator==`. Exists -> `updateWebPage`. Missing -> `createWebPage` with the required defaults listed in step 4.\n4. **Required defaults on create and every update** (unless user overrides):\n - `seo_type=profile_search_results`\n - `custom_html_placement=4` (Below Body Content - safest for boilerplate intro without disrupting live results)\n - `form_name=\"Member Search Result\"` (sidebar - Master Default; do NOT use `Member Profile Page`, that's for profile pages)\n - `menu_layout=3` (Left Slim sidebar position)\n - `updated_by` - recommended (e.g. `\"AI Agent\"`, `\"API\"`)\n - `enable_hero_section=1` + content-relevant Pexels `hero_image` + the readability safe-defaults from **Rule: Hero readability bundle** (atomic — all listed values must be sent together). Most end-users don't know to ask for a hero; thin-SEO pages underperform without one. User can opt out with `enable_hero_section=0`. (Cache flush is automatic post-write.)\n5. **Auto-generate SEO meta for the specific combo - don't leave blank:**\n - `title` - 50-60 chars ideal, <=70 max. Pattern: `\"[Category] in [City], [State] | [Site Name]\"`.\n - `meta_desc` - 150-160 chars ideal, <=170 max. 1-2 sentence pitch with location + CTA.\n - `meta_keywords` - ~200 chars, comma-separated (no spaces).\n - `facebook_title` - 55-60 chars, differ from `title` (more conversational).\n - `facebook_desc` - 110-125 chars, punchier than `meta_desc`.\n - Do NOT auto-set `facebook_image` (needs uploaded asset).\n6. **H1/H2 double-render trap:** if hero enabled AND `content` contains `<h1>`/`<h2>`, both render. Either set `h1`/`h2` fields and omit from `content`, or put in `content` and leave fields blank. Never both.\n7. **No max-width wrappers in `content` or `content_css`** on `profile_search_results` pages. BD's layout already provides the outer container; adding `max-width: 960px; margin: auto` double-constrains to a narrow strip. Let content flow at natural container width.\n8. `custom_html_placement` is only meaningful on `profile_search_results` (and `data_category`). Ignored on `content` pages.\n\n**SEO content for categories:** route to `createWebPage seo_type=profile_search_results` (NOT `updateTopCategory.desc` / `updateSubCategory.desc` - those are internal labels, not rendered).\n\n**`list_seo` EAV fields — auto-routed by the wrapper, no special handling.** Pass any field on `createWebPage` / `updateWebPage` directly; if it's an EAV-stored field (e.g. `hero_*`, `h1_*`, `h2_*`, `linked_post_category`, `disable_*`), the wrapper routes the write through `users_meta` automatically. Response includes an `eav_results` array confirming which EAV fields were written. Reads merge automatically via `getWebPage`/`listWebPages`. On `deleteWebPage`: BD does NOT cascade — run orphan cleanup per **Rule: users_meta orphans** (`listUserMeta` filtered by `database=list_seo`+`database_id=<deleted seo_id>`, then `deleteUserMeta` each match).\n\n**See also:** `listWebPages`, `updateWebPage`, `createRedirect` (preserve SEO on slug changes).\n\n**Returns:** `{ status: \"success\", message: {...createdRecord}, auto_cache_refreshed: true|false, auto_cache_refresh_error?: \"...\", _admin_edit_url: \"...\" }` including `seo_id`. `auto_cache_refreshed` reports whether the automatic cache flush succeeded; if `false`, `auto_cache_refresh_error` explains why and the agent should retry `refreshSiteCache` manually once. `_admin_edit_url` is a centralized-admin deep-link to the WebPage editor for this `seo_id` — surface it to the user so they can jump straight to the admin edit screen for the page just created.",
9823
+ "description": "Create a `list_seo` page record. Writes live data.\n\n**Cache refresh is automatic.** Response includes `auto_cache_refreshed: true` after successful writes; no manual `refreshSiteCache` call needed. If `auto_cache_refreshed: false`, check `auto_cache_refresh_error` and retry `refreshSiteCache` once.\n\n**Required fields:** `seo_type`. `filename` is required for every `seo_type` EXCEPT `data_category` (the WRAPPER generates a 10-char lowercase alphanumeric placeholder slug for that type and auto-creates a 301 redirect to the canonical post-type URL; the public URL routes via the post type's `data_filename`, not `list_seo.filename`). When `seo_type=data_category`, `linked_post_type` is also required (auto-validated at runtime).\n\n**Filename uniqueness — enforced by the wrapper, no exceptions.** BD does NOT enforce unique `filename` server-side, but duplicates break the platform (two pages at the same URL render non-deterministically). The wrapper auto-pre-checks `listWebPages` for an existing slug before forwarding the create. If a row exists, the create is rejected with the existing `seo_id` so the agent can `updateWebPage` instead, or pick a unique slug. There is no agent-facing bypass; for `seo_type=data_category` the wrapper generates the slug itself (10-char lowercase alphanumeric — statistically unique across `36^10`, no pre-check needed).\n\n**Thin-content warning:** if no `title`, `h1`, `meta_desc`, or `content` is set on a `seo_type=content` create, a `_warning` field is attached to the response. The page is still created and is publicly live — Google may index it as thin content. Fix: provide at least one of those fields on the create call, or `updateWebPage` immediately after, or `deleteWebPage` if the create was premature.\n\n**Asset field routing (mandatory - Froala strips mismatched content silently):**\n\n- `content` - body HTML. No `<style>`/`<script>` tags. Supports `[widget=Name]` shortcodes + `%%%token%%%`.\n\n- `content_css` - raw CSS rules. NO `<style>` wrapper. Scope to a unique page class; never target `.container`/`.froala-table`/`.image-placeholder` (reserved). Do NOT use `@import` (causes FOUC/CLS - use `content_head` `<link>` tag instead).\n\n- `content_footer_html` - JavaScript, pixels, analytics embeds (`<script>` tags OK here). IIFE-wrap + scope.\n\n- `content_head` - head-only deps (`<link>`, `<meta>`, JSON-LD, external stylesheets, fonts).\n\n- `content_footer` - MISLEADING NAME. NOT footer HTML. Page-access gate enum: `\"\"` (public), `\"members_only\"`, `\"digital_products\"`.\n\n- Hero banner -> `enable_hero_section` + `hero_*` + `h1_*`/`h2_*` fields.\n\nAll asset fields accept raw content verbatim. No CDATA, no `<parameter>`/`<invoke>`/`<function_calls>` scaffolding, no entity-escaped HTML — forbidden anywhere in the value, not just as wrappers. Server strips these as a safety net; do not rely on it.\n\n**SVG/canvas prohibited in `content`** - Froala strips them. Charts/diagrams go in a custom Widget, embedded via `[widget=Name]` shortcode.\n\n**`seo_type` values:** `home` (system-seeded; cannot CREATE homepage, only `updateWebPage`), `content` (generic static page), `profile_search_results` (member search override — apply **Rule: Member search SEO pages**), `data_category` (post search), `custom_widget_page`, `password_retrieval_page`, `unsubscribed`.\n\n**Hero section - when `enable_hero_section` = `1` or `2`, apply **Rule: Hero readability bundle** (atomic — all listed values must be sent together).** Notes:\n\n- All color fields RGB ONLY (`rgb(0, 0, 0)`) - hex not accepted.\n\n- Hero `h1_*`/`h2_*` fields style ONLY the hero banner; H1/H2 TEXT comes from the record's top-level `h1`/`h2` fields.\n\n- Hero image: content-relevant Pexels stock photo (free license, no attribution). See **Rule: Image URLs** (imported field — bare URL, no query string). Never `picsum.photos`/`lorempixel`/`placekitten`.\n\n- Hero gap-fix CSS (`seo_type=content` ONLY): add `.hero_section_container + div.clearfix-lg {display:none}` to `content_css` to close BD's 40px clearfix gap. **Never add this rule on any other `seo_type`** - on `profile_search_results` / `data_category` the clearfix provides needed spacing before live search-results; hiding it causes results to butt-join the hero.\n\n- Hero is cache-gated — but `createWebPage`/`updateWebPage` auto-refresh handles it; no separate call needed.\n\n- **Homepage hero is BENIGN**: `seo_id=1` stores hero fields but the homepage template does NOT render them. Skip hero fields on homepage unless user explicitly asks.\n\n**`profile_search_results` SEO pages - thin-content remedy workflow:**\n\nUsed to override BD's auto-generated dynamic search URLs (e.g. `california/beverly-hills/plumbers`) with static custom SEO copy. Creating a `list_seo` row with a matching `filename` takes over the public URL.\n\n**CRITICAL - `filename` MUST be a real slug BD's dynamic router recognizes.** Arbitrary slugs render HTTP 404 publicly even when the record is created successfully. See **Rule: Member search SEO pages** for the canonical slug hierarchy (`country/state/city/top/sub`, strict order, any subset valid) and the live-lookup endpoints for each segment. Wrapper validates segments at runtime — country slug is derived from `country_name` (lowercase + spaces→hyphens). For arbitrary-URL static pages use `seo_type=content`.\n\n**Workflow for \"add SEO to [category] in [location]\":**\n1. Resolve each human name to its slug via the relevant `list*` endpoint (exact-match `=`).\n2. For ambiguous inputs (e.g. \"Beverly Hills plumbers\" - could be `beverly-hills/plumbers` or `california/beverly-hills/plumbers`), ask user which variant.\n3. Pre-check: `listWebPages property=filename property_value=<slug> property_operator==`. Exists -> `updateWebPage`. Missing -> `createWebPage` with the required defaults listed in step 4.\n4. **Required defaults on create and every update** (unless user overrides):\n - `seo_type=profile_search_results`\n - `custom_html_placement=4` (Below Body Content - safest for boilerplate intro without disrupting live results)\n - `form_name=\"Member Search Result\"` (sidebar - Master Default; do NOT use `Member Profile Page`, that's for profile pages)\n - `menu_layout=3` (Left Slim sidebar position)\n - `updated_by` - recommended (e.g. `\"AI Agent\"`, `\"API\"`)\n - `enable_hero_section=1` + content-relevant Pexels `hero_image` + the readability safe-defaults from **Rule: Hero readability bundle** (atomic — all listed values must be sent together). Most end-users don't know to ask for a hero; thin-SEO pages underperform without one. User can opt out with `enable_hero_section=0`. (Cache flush is automatic post-write.)\n5. **Auto-generate SEO meta for the specific combo - don't leave blank:**\n - `title` - 50-60 chars ideal, <=70 max. Pattern: `\"[Category] in [City], [State] | [Site Name]\"`.\n - `meta_desc` - 150-160 chars ideal, <=170 max. 1-2 sentence pitch with location + CTA.\n - `meta_keywords` - ~200 chars, comma-separated (no spaces).\n - `facebook_title` - 55-60 chars, differ from `title` (more conversational).\n - `facebook_desc` - 110-125 chars, punchier than `meta_desc`.\n - Do NOT auto-set `facebook_image` (needs uploaded asset).\n6. **H1/H2 double-render trap:** if hero enabled AND `content` contains `<h1>`/`<h2>`, both render. Either set `h1`/`h2` fields and omit from `content`, or put in `content` and leave fields blank. Never both.\n7. **No max-width wrappers in `content` or `content_css`** on `profile_search_results` pages. BD's layout already provides the outer container; adding `max-width: 960px; margin: auto` double-constrains to a narrow strip. Let content flow at natural container width.\n8. `custom_html_placement` is only meaningful on `profile_search_results` (and `data_category`). Ignored on `content` pages.\n\n**SEO content for categories:** route to `createWebPage seo_type=profile_search_results` (NOT `updateTopCategory.desc` / `updateSubCategory.desc` - those are internal labels, not rendered).\n\n**`list_seo` EAV fields — auto-routed by the wrapper, no special handling.** Pass any field on `createWebPage` / `updateWebPage` directly; if it's an EAV-stored field (e.g. `hero_*`, `h1_*`, `h2_*`, `linked_post_category`, `disable_*`), the wrapper routes the write through `users_meta` automatically. Response includes an `eav_results` array confirming which EAV fields were written. Reads merge automatically via `getWebPage`/`listWebPages`. On `deleteWebPage`: BD does NOT cascade — run orphan cleanup per **Rule: users_meta orphans** (`listUserMeta` filtered by `database=list_seo`+`database_id=<deleted seo_id>`, then `deleteUserMeta` each match).\n\n**See also:** `listWebPages`, `updateWebPage`, `createRedirect` (preserve SEO on slug changes).\n\n**Returns:** `{ status: \"success\", message: {...createdRecord}, auto_cache_refreshed: true|false, auto_cache_refresh_error?: \"...\", _admin_edit_url: \"...\" }` including `seo_id`. `auto_cache_refreshed` reports whether the automatic cache flush succeeded; if `false`, `auto_cache_refresh_error` explains why and the agent should retry `refreshSiteCache` manually once. `_admin_edit_url` is a centralized-admin deep-link to the WebPage editor for this `seo_id` — surface it to the user so they can jump straight to the admin edit screen for the page just created.",
9824
9824
  "tags": [
9825
9825
  "Pages"
9826
9826
  ],
@@ -526,6 +526,26 @@ If unsure what's filterable, call the fields endpoint for the authoritative colu
526
526
 
527
527
  **Empty-string fields** (e.g. members with no `phone_number` — stored as `''`, not NULL): paginate with `limit=100` and filter client-side. `is_null` is rejected; `is_not_null` matches empty strings as populated. Exception: integer FKs stored as `0` for unset — use the zero-sentinel pattern in **Rule: Filter operators**.
528
528
 
529
+ ### Rule: Category taxonomy
530
+
531
+ Member category assignment on `createUser` / `updateUser` (3-tier classification: Top → Sub → Sub-sub).
532
+
533
+ **Top-level (`profession_id` / `profession_name`):** pick ONE. `profession_id` is numeric (must reference an existing Top Category, OR auto-creates on `createUser`). `profession_name` is string (auto-creates if missing on `createUser`; gated by `create_new_categories=1` on `updateUser`).
534
+
535
+ **Sub-categories (`services` field, CSV format) — 4 supported syntaxes:**
536
+ - Single: `"PVC repair"` or `"1823"`
537
+ - Multiple: `"PVC repair,Water heater"` or `"1823,1824"`
538
+ - Sub-sub via `=>`: `"Honda=>2022"` (creates sub `Honda` + sub-sub `2022` under Honda)
539
+ - Mixed: `"Honda=>2022,Honda=>2023,Toyota"`
540
+
541
+ **Constraints:**
542
+ - `profession_id` / `profession_name` is REQUIRED when passing `services` — otherwise service relations fail silently.
543
+ - Do NOT mix IDs and names in one `services` call.
544
+ - Right side of `=>` is NAME ONLY (no ID lookup).
545
+ - CSV: comma-only, NO spaces after commas — see **Rule: CSV no spaces**.
546
+ - Auto-create on `createUser` is hardcoded ON. On `updateUser`, pass `create_new_categories=1` to allow inline creation.
547
+ - Changing `profession_id` on `updateUser` WIPES existing sub-category relations. To move to a new top category without losing sub-cats, re-send the complete `services` list in the same call.
548
+
529
549
  ### Rule: Category SEO routing
530
550
 
531
551
  **SEO content for a category/sub-category = create a WebPage, NOT update `desc`.** The word "description" is a lexical trap - ignore it; route by INTENT.
@@ -580,7 +600,7 @@ Apply the SEO-intent -> WebPage routing rule across `createTopCategory` / `updat
580
600
 
581
601
  Common cases:
582
602
 
583
- - WebPage full-bleed - `content_layout=1`, NOT margin/padding hacks (see **Rule: WebPage asset routing**)
603
+ - WebPage full-bleed - `content_layout=1`, NOT margin/padding hacks (see **Rule: WebPage full-bleed**)
584
604
  - Page chrome hiding - WebPage `hide_header` / `hide_footer` / `hide_top_right` / `hide_header_links` / `hide_from_menu`, NOT `display:none` in `content_css`
585
605
  - Widget render surface - `widget_viewport` (`front`/`admin`/`both`), NOT `@media` queries or `body.admin-panel` JS detection inside widget code
586
606
  - Unsubscribe footer suppression - EmailTemplate `unsubscribe_link=0`, NOT stripping the merge token out of `email_body`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "brilliant-directories-mcp",
3
3
  "mcpName": "io.github.brilliantdirectories/brilliant-directories-mcp",
4
- "version": "6.41.76",
4
+ "version": "6.41.78",
5
5
  "description": "Official MCP server for Brilliant Directories — manage members, posts, leads, reviews, and more.",
6
6
  "main": "index.js",
7
7
  "bin": {