Skip to main content
Glama

ZAH Site MCP

Lets a client's own AI read and build on their ZAH-built website through MCP, and gives Zah Editor a real Publish so edits reach every visitor.

Do not rebuild this per site. Install it and mount it. If the contract below does not fit a site, extend it here so every site gets the change.

The model, in one paragraph

The files Zah shipped are the permanent default. Everything the client's AI does is an overlay on a Railway volume: new pages, new sections, layout, CSS, images, video, embeds and forms. reset_page and reset_site put the build back at any time; every write is versioned and revert restores any version. Forms go to ZAH CRM by default. The host declares its lead seam (crm: { leadPath: '/api/lead', enabled }); when Zah has switched the CRM on, a form with that action sends every submission to the client's CRM as a lead. When it is not on, the form is kept but inert and the AI is told to ask the client "would you like this form connected to ZAH CRM?" and send them to Zah. The client's other option is their own outside service (Formspree, Google Forms, Airtable) via an https action. This server keeps no form data and nothing can be built on it through the MCP. Storage stops at a quota.

Related MCP server: Sophia Stack MCP

Install

npm i github:Yawitazah/zah-site-mcp

Alpine images need git for that: RUN apk add --no-cache git in the Dockerfile.

Mount (the whole integration)

const path = require('path');
const zahSite = require('zah-site-mcp');

const site = zahSite.mount(app, {
  siteId: 'new-vision',                              // matches SITE_ID / ZAH Gate
  name: 'New Vision Therapy & Wellness',
  dataDir: process.env.DATA_DIR || '/data',          // a Railway volume
  token: process.env.SITE_MCP_TOKEN,                 // the client's AI presents this
  adminHash: process.env.EDITOR_ADMIN_HASH,          // the SPARE key: sha256(email:password)
  accountUrl: process.env.ACCOUNT_URL,               // ZAH Account; the client's own login (default zahbrandsolutions.com)
  publicUrl: process.env.PUBLIC_URL,
  pages: [
    { path: '/', file: path.join(__dirname, 'index.html'), root: 'main' },
  ],
  settings: {                                        // values the client may change
    bookingUrl: { label: 'Where "Book" buttons go', kind: 'url',   default: process.env.BOOKING_URL },
    phone:      { label: 'Contact phone',           kind: 'phone', default: process.env.CONTACT_PHONE },
  },
  quotaMb: 250, maxFileMb: 30,                       // the client's storage
  crm: { leadPath: crm.leadPath, enabled: crm.leadsEnabled, notice: '...' },   // the ZAH CRM door for forms; notice = a host rule every form note carries (e.g. no health information)
});
// ...then ZAH Pay (reads site.settings()), express.static and the 404 handler.

Rules:

  • Mount before express.static and before any product that reads site.settings(). It serves the listed pages and every client-created page.

  • The first page in pages is the template: client-created pages take its head, CSS, header, footer and chrome.

  • root is the selector Zah Editor edits (usually main). The MCP edits the whole body: header, nav, main, footer.

  • Each page needs a file on disk. Redeploying new source changes a page that has NOT been restructured; a restructured page keeps its snapshot until reset_page. Say so in the handoff.

On the page, after zah-editor.js:

<script src="/zah-site/publish.js"></script>

The one login (0.5.0)

The client already has a login: the email and password they made at zahbrandsolutions.com/account, the one that opens their billing, their desk and their Dispatch board. It now opens the pencil on their own site too. Zah, 2026-09-10: "If they are paying for it they should have access without having to create multiple logins."

POST /zah-site/login { email, password } takes two kinds of answer:

Their ZAH Account

asked server to server at POST <accountUrl>/api/account/site-login { siteId, email, password }. ZAH Account checks the password and that this account's live plans include this siteId, so nobody signs in to a site they do not pay for.

adminHash

the spare key. Needs no network, so a bad day at head office never locks a client out of their own page. Zah's way in.

Either way the answer is { token, who, via } and the token is the site's own SITE_MCP_TOKEN. Five wrong answers a minute from one address earns a 429; right answers never count. Set accountLogin: false to switch the account route off for a site.

The back office, on the client's own domain

A client looks for their things on their site, not on ours. Five short paths are opened unless a real page already lives there, each noindex, each landing on the one login:

Path

Goes to

/account, /login

zahbrandsolutions.com/account

/edit

/?edit=1, which opens the editor's login on arrival

/dispatch

ZAH Account's signed hand-off to their Dispatch board, no second password

/crm

the same hand-off to ZAH CRM

backOffice: false turns them all off; backOffice: { crm: false } drops one; backOffice: { dispatch: '/board' } moves one. A site that serves its own page at one of these paths silently keeps it.

What it owns

Thing

Where

Routes

/zah-site/*: MCP at /zah-site/mcp, assets at /zah-site/assets/<name>, REST beside them

Pages

the configured ones, plus any path the client created

Data

<dataDir>/zah-site/overlay.json, history/ (last 200 versions), assets/

Styling

none of its own. Custom CSS the client adds is injected as <style id="zs-custom">

Chrome

<script>s and product UI from the file (#edToggle, #edBar, .zp-modal, [data-zs-chrome]) are never in a snapshot and never lost

Environment

Variable

Required

What

SITE_MCP_TOKEN

for MCP + publish

The site's own token. Generate: python -c "import secrets;print('zs_'+secrets.token_hex(24))". Rotate to revoke. Never Zah's CRM key, never a Stripe key.

DATA_DIR

yes on Railway

Mount a volume at /data and set this to /data, or edits and uploads vanish on redeploy

EDITOR_ADMIN_HASH

no

The spare key: sha256(email.lower():password), the same hash the page's ZAH_EDITOR_CFG carries. The client's ZAH Account works without it.

ACCOUNT_URL

no

Where ZAH Account lives. Defaults to https://www.zahbrandsolutions.com; set it only to point a site at a rehearsal.

Without a token: pages serve, everything under /zah-site/* refuses (503), and the editor falls back to adminHash and localStorage.

Connect a client's AI

claude mcp add --transport http my-site https://YOUR-SITE/zah-site/mcp --header "Authorization: Bearer zs_..."

claude.ai custom connectors cannot set headers; use the keyed URL: https://YOUR-SITE/zah-site/mcp/k/zs_...

The tools

Read

get_site

id, pages, settings, usage, and how editing works. Call first.

list_pages

every page, built or client-created

get_outline(page)

header, nav, main, sections, footer, blocks: keys, classes, depth

list_content(page)

every heading, paragraph, item, link, button, image, video with a key

get_content(page, key) / get_html(page, key)

one element; its HTML, or the whole body

get_styles

the site's CSS plus custom CSS, so new markup looks native

forms_info

is ZAH CRM connected, the action and fields to use, what to tell the client if not

Words, links, images

set_text set_link set_image set_hidden set_many

small edits; child elements (icons) kept

Structure

insert_html(page, html, position, target)

a new section, card, image, embed, form

set_html(page, key, html)

rewrite an element, or the whole body

move duplicate remove

<main>, <header>, <footer> cannot be removed

Pages, style, files

create_page(path, title, description?, from?, html?)

starts as a copy of from (default /) so it matches

set_page_meta(page, title?, description?, image?) get_page_meta (REST: GET/POST /zah-site/page-meta?page=)

SEO for any page: title, description, share image (og:image). delete_page only removes client-created pages

set_css append_css

the site-wide custom stylesheet

add_asset(name, url | dataBase64) list_assets delete_asset get_usage

images, video, audio, PDF; per-file cap and site quota

Settings and safety

get_settings set_setting

values the host declared (booking link, phone). Empty resets to default

history revert(version)

every version kept

reset_page(page) reset_site(confirm)

back to the build; assets and settings survive reset_site

Keys

Every editable element (h1 h2 h3 h4 h5 p li blockquote figcaption a button img video label) and structural block (header nav main section article aside footer figure ul ol table, and div with an id or class) inside <body> gets a data-zs key. Untouched pages use positional keys (h2:3, section:1). Once a page has been restructured ("materialised"), keys are baked into its markup and new nodes get random keys (n:3f9a1c), so later insertions never shift anything. An element that already carries data-zs="hero.title" keeps that name.

Order of application

  1. snapshot (the page as the client restructured it) or the file's body

  2. chrome from the file re-attached

  3. keys

  4. small keyed edits

  5. custom CSS

A Zah Editor publish replaces only the editor's root (main) inside that model, so header and footer edits made by the AI survive an editor save.

What the sanitiser does

Allowed: any HTML, inline styles, <style>, external and inline <script> (embeds, widgets), <iframe> from https, <form> posting to the ZAH CRM lead path (when connected) or to an https address that is not this site. Refused: javascript: URLs, on* handler attributes, srcdoc, object/embed/applet/base/meta. Any other <form> is kept visible but made inert with a note in data-zs-inert saying why. Every structural result lists the forms it contained with a status (zah-crm, zah-crm-off, external, inert) and a note for the client.

Failure modes already met

  • "Saved here only" in the editor. No token reached the bridge: the page's admin hash does not match EDITOR_ADMIN_HASH, or SITE_MCP_TOKEN is unset.

  • Edits vanished after a deploy. No volume. Set DATA_DIR to a mounted path.

  • Zah's source change is not showing on a page. That page is materialised; the client restructured it. reset_page shows the new build (their changes go to history and can be reverted, or redone).

  • claude.ai cannot connect. It cannot send headers; use /mcp/k/<token>.

  • Video upload fails. Base64 through MCP is limited to a few MB; give add_asset a URL instead (Google Drive direct link, Dropbox ?dl=1), or embed from YouTube/Vimeo.

Test

npm test

Fifty checks: keys, keyed edits, structure, pages, CSS, assets and quota, forms policy, settings, editor snapshot, chrome survival, history, resets, auth.

The house credit

Every page a site serves carries a link to zahbrandsolutions.com. Mark the one in the footer data-zs-credit and it is chrome: not keyed, not editable by the AI or the editor. If a page ends up with no link to the house at all (a client page without a footer, an edit that removed it), render() adds a small one before </body>. mount({ credit: false }) is the only switch, and it is Zah's.

Publishing and upgrades (0.6.0)

Use root: 'body' in both the mount and ZAH_EDITOR_CFG to edit the header, navigation, main content, footer and mobile contact bar. Zah Editor 1.2.0 excludes its own toolbar/scripts from edits and undo snapshots.

Normal text, link and style saves are keyed patches. They no longer freeze the entire page, so a later source build can add features without discarding client text. Keep explicit data-zs keys in source stable. A source edit must not rename a client's edited key or replace it with positional numbering. Structural edits still create snapshots; never reset a client snapshot just to make a new source build visible. Export it, compare, and migrate deliberately.

Saves carry the rendered version and source hash. A stale tab receives 409 and keeps its unsaved work visible. Save only reports Published after the server confirms. Reset waits for confirmation and successful completion; cancelling it sends no request. Server-managed pages do not load an old localStorage draft over published content.

Optional settings bindings on page elements:

<a data-zs="contact.phone" data-zs-setting="phone"
   data-zs-setting-text data-zs-setting-href="tel"
   href="tel:+15551234567">555.123.4567</a>

data-zs-setting-text binds the visible text; data-zs-setting-href supports tel, sms, and mailto. data-zs-setting-prefix="call " preserves a label prefix. Saved setting values update every binding. An editor patch to a bound value also updates that setting. With no saved setting, source/overlay text remains authoritative.

Authenticated maintenance endpoints:

  • GET /zah-site/export exports current content/settings; ?version=N exports a history version. Treat exports as private client data.

  • POST /zah-site/rebase atomically replaces one old snapshot with reviewed keyed edits on the current source. Requires page, baseVersion, SHA-256 sourceHash, edits (keyed patch object), and confirm: "replace-snapshot-with-reviewed-edits". The complete previous state remains in history; concurrent changes refuse with 409. Export and compare first. This is a maintenance operation, not a routine deploy step.

MCP is standard Streamable HTTP. Any compatible AI client can use the endpoint with a Bearer token; a keyed URL supports clients without custom headers. Keep these URLs/tokens out of shared reports and source control.

Hosts with scroll animations can declare ZAH_EDITOR_CFG.transientClasses and transientStyles arrays (for example in, stuck, and --sd). These are stripped from the comparison baseline and published markup, so scrolling does not turn a text edit into a structural snapshot.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for AI-assisted WordPress editing across 12 page builders. 172 tools for content management, page builder editing, WooCommerce, SEO analysis, accessibility scanning, and site intelligence. Edits native builder formats (Elementor, Bricks, Divi, Gutenberg, Beaver Builder, and 7 more) with duplicate-before-edit safety, optimistic locking, and surgical element-level operations
    9
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to interact with WordPress sites over MCP, providing read-only tools for AEO/GEO readiness, AI visibility, traffic, request logs, bot identification, page checks, and schema/markdown previews, plus opt-in write tools to draft, edit, and publish posts with permission checks and auditing.
    2
    GPL 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to draft, review, preview, and publish content across multiple websites through typed MCP tools. Ensures safe, policy-gated, and auditable publication without requiring a conventional CMS admin interface.
    Apache 2.0