> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP servers

> How gavAI exposes itself to Claude, Cursor, and other MCP clients — what each server does, what it costs you in tokens, and which one to pick.

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const Path = ({icon, title, href, children}) => <a className="gv-choose-path__card" href={href}>
    {icon && <span className="gv-choose-path__icon" aria-hidden="true">
        <Icon icon={icon} />
      </span>}
    <h4 className="gv-choose-path__title">{title}</h4>
    {children && <p className="gv-choose-path__body">{children}</p>}
    <svg className="gv-choose-path__arrow" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <line x1="7" y1="17" x2="17" y2="7" />
      <polyline points="7 7 17 7 17 17" />
    </svg>
  </a>;

export const ChoosePath = ({eyebrow = "CHOOSE YOUR PATH", label, children}) => <section className="gv-choose-path">
    <div className="gv-choose-path__header">
      <Eyebrow label={eyebrow} />
      {label && <h3 className="gv-choose-path__label">{label}</h3>}
    </div>
    <div className="gv-choose-path__grid">{children}</div>
  </section>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="CONCEPT" />

<PageMeta audience="Developers integrating with AI clients" readingTime="5 min" prereqs={["gavAI account", "Claude Desktop or another MCP client"]} level="Beginner" />

AI assistants like Claude are most useful when they can act, not just answer. The Model Context Protocol (MCP) is the wire format that lets them — the assistant connects to a local server process, asks "what tools do you have?", and starts calling them. gavAI ships two MCP servers so any MCP-aware client can read and edit pages, rotate keys, fetch logs, and otherwise drive the platform from a chat window. By the end of this page you will know what MCP is, why we ship two servers instead of one, and which one to point at your workspace.

<ChoosePath label="Pick the MCP server that matches the job">
  <Path icon="edit-3" title="I want Claude to edit pages and themes" href="/mcp/builder-mcp">
    Builder MCP — 14 hand-curated tools, bundled with the CLI.
  </Path>

  <Path icon="globe" title="I want Claude to drive the full REST surface" href="/mcp/public-api-mcp">
    Public API MCP — every tagged OpenAPI endpoint becomes a tool.
  </Path>
</ChoosePath>

## What MCP is, in 30 seconds

MCP is a small JSON-RPC protocol that an AI client (Claude Desktop, Cursor, Windsurf) speaks to a tool-providing server. The server runs as a local subprocess, advertises a list of tools, and the client invokes them with structured arguments. Every gavAI MCP tool call ends in an HTTP request to gavAI's API — the server is a translation layer, not a separate runtime.

```mermaid theme={null}
sequenceDiagram
    participant C as Claude (client)
    participant M as gavAI MCP server
    participant A as gavAI API
    C->>M: tools/list
    M-->>C: [list_tenants, patch_tenant_app, ...]
    C->>M: tools/call patch_tenant_app { slug: "acme", operations: [...] }
    M->>M: load credential (CLI session or GAVAI_TOKEN)
    M->>A: PATCH /v1/workspaces/acme/app
    A-->>M: 200 OK
    M-->>C: { status: "applied" }
```

Two properties matter:

* **The server holds the credential, not the client.** Claude never sees your API key or session token.
* **Every call has the same authorization as a direct API call.** The MCP server doesn't widen scopes — if your token can't publish a workspace, neither can Claude.

## The two servers and how they differ

gavAI ships two MCP servers because they serve two different jobs and want different credentials.

<FeatureTable
  columns={[
{ key: "server", label: "Server" },
{ key: "transport", label: "Transport" },
{ key: "auth", label: "Auth" },
{ key: "purpose", label: "What it's for" }
]}
  rows={[
{
  server: "`@gavai/builder-mcp`",
  transport: "stdio (JSON-RPC)",
  auth: "Reuses `~/.config/gavai/credentials.json` — run `gavai login` once",
  purpose: "Builder operations — pages, blocks, components, themes, tenant CRUD"
},
{
  server: "`@gavai/mcp-server`",
  transport: "stdio (JSON-RPC)",
  auth: "`GAVAI_TOKEN` env var (`gak_live_*` or `gak_test_*`)",
  purpose: "OpenAPI-driven — every tagged REST endpoint becomes an MCP tool"
}
]}
/>

The Builder MCP is hand-curated: 14 tools that map to the operations a page editor actually performs. It is bundled with the gavAI CLI, so it inherits your CLI session and refreshes the token when it expires. Reach for it when an AI assistant should behave like a copilot inside the Builder.

The Public API MCP is generated from the OpenAPI spec. Every endpoint tagged `x-mcp-tool: true` becomes a tool — including operations the Builder doesn't expose (rotate a key, mint a domain, query logs). It authenticates with a workspace API key you mint in the console. Reach for it when the AI is doing platform-wide work that goes beyond editing pages.

## Which to use

<Columns cols={2}>
  <BrandCard eyebrow="BUILDER MCP" title="Edit pages, themes, blocks" href="/mcp/builder-mcp">
    14 hand-curated tools. Bundled with the CLI. Auth piggybacks on your CLI session.
  </BrandCard>

  <BrandCard eyebrow="PUBLIC API MCP" title="Drive the full REST surface" href="/mcp/public-api-mcp">
    Auto-generated from the OpenAPI spec. Auth via a workspace API key.
  </BrandCard>
</Columns>

You can run both at the same time — they register under different names in the client config, and the AI picks whichever exposes the tool it needs. See [Set up Claude Desktop](/mcp/claude-desktop) for the side-by-side config.

## What you should know before connecting one

<ArrowList>
  <ArrowItem>**The token's scope is the AI's ceiling.** An MCP server can do anything its credential can do. Mint a least-privilege API key for the Public API MCP; don't reuse a long-lived admin key.</ArrowItem>
  <ArrowItem>**Treat the connected workspace like production.** AI clients chain tool calls, and a chain ending in `publish_tenant_version` reaches your live site. Point experimentation at a `gak_test_*` key on a test workspace.</ArrowItem>
  <ArrowItem>**Revoke when you're done.** Builder MCP — run `gavai logout` or revoke the session in the console. Public API MCP — delete the key via `DELETE /v1/workspaces/{slug}/tokens/{id}` or in the console.</ArrowItem>
  <ArrowItem>**Tool discovery is part of the protocol.** Clients call `tools/list` at startup; tools that need scopes the credential doesn't have will still appear, but calling them returns a permission error.</ArrowItem>
</ArrowList>

## Where to next

<Columns cols={2}>
  <BrandCard eyebrow="HOW-TO" title="Set up Claude Desktop" href="/mcp/claude-desktop">
    The end-to-end config for one or both servers, plus troubleshooting.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Builder MCP tools" href="/mcp/builder-mcp">
    The 14 Builder tools, their inputs, and a worked example.
  </BrandCard>

  <BrandCard eyebrow="REFERENCE" title="Public API MCP coverage" href="/mcp/public-api-mcp">
    How OpenAPI operations become MCP tools, plus scope guidance.
  </BrandCard>

  <BrandCard eyebrow="CLI" title="gavai mcp" href="/cli/commands/mcp">
    The CLI subcommand that hosts the Builder MCP and writes the client config.
  </BrandCard>
</Columns>
