# LightCMS [![CI](https://github.com/metavert-io/lightcms/actions/workflows/ci.yml/badge.svg)](https://github.com/metavert-io/lightcms/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/metavert-io/lightcms/branch/main/graph/badge.svg)](https://codecov.io/gh/metavert-io/lightcms) [![Go Report Card](https://goreportcard.com/badge/github.com/metavert-io/lightcms)](https://goreportcard.com/report/github.com/metavert-io/lightcms) [![lightcms MCP server](https://glama.ai/mcp/servers/jonradoff/lightcms/badges/score.svg)](https://glama.ai/mcp/servers/jonradoff/lightcms) LightCMS is a Go-powered content management system built for the AI era. It's simultaneously **AI-native** (semantic search, built-in Claude-powered chat widget, MCP server for agent control), **agentically controllable** (Claude Code and any MCP client can read, write, publish, and bulk-import content via 130 MCP tools), and **agentically updatable** (the codebase is clean, well-structured Go — coding agents can safely extend it). For teams that want a CMS that works with AI rather than around it. ## What's New in v7.3 — SEO & AI | Feature | Summary | |---------|---------| | **AI Traffic** | See which AI crawlers read your site (training vs. AI search vs. user-initiated fetches), what they read most, and how many visits ChatGPT, Perplexity, Claude, Gemini and Copilot send you. | | **AI Crawler Policy** | Allow or block AI training, AI search and user fetches separately, with per-crawler overrides and an optional Content-Signal line, rendered into robots.txt. | | **Markdown Copies** | Every page is also available as clean Markdown at `/.md`, linked from the page and from llms.txt, so agents can read it without the HTML. | | **Richer Structured Data** | Authors, publisher with logo, breadcrumbs, FAQ markup, and modified dates that only move when the page content actually changes. | | **RSS & Atom Feeds** | `/feed.xml`, `/atom.xml` and per-collection feeds with full content. | | **Hide from Search & AI** | Per-page noindex that also removes the page from the sitemap, llms.txt, feeds and IndexNow. | ## What's New in v7.2 | Feature | Summary | |---------|---------| | **IndexNow** | Published, edited, moved and deleted pages are pushed to Bing, Yandex and other IndexNow engines right away. Zero-config: each install generates its own key, verifies it owns its `BASE_URL` before submitting, and submits the whole site once on first activation. Status and history at Tools → IndexNow. | | **CMS Agent** | Configurable email digests (via Resend) covering site health, traffic, review queue, agent activity and broken links, with optional AI commentary. | ## What's New in v7.1 | Feature | Summary | |---------|---------| | **Ambient Copilot** | The copilot is a slide-in drawer on every admin page — floating 🤖 button, fullscreen toggle, ChatGPT-style searchable chat history, live typing indicator, and rendered tables. It edits, creates, publishes, and reads analytics and maintenance reports in plain language. | | **Analytics Ranges** | 60/90-day presets and custom date ranges across all analytics views. | | **Fast AI-Crawler Endpoints** | llms.txt serves in ~1 second on large sites (projected queries), and homepages emit schema.org WebSite JSON-LD with SearchAction. | | **Version Visibility** | The admin sidebar shows the running version. | ## What's New in v7.0 | Feature | Summary | |---------|---------| | **Agent Sandbox ("PRs for content")** | Agents work in an isolated fork with copy-on-write; a person reviews a complete per-field diff and merges exactly what was reviewed. The server enforces it: a Sandbox key or connection can't change the live site at all. | | **Agent Governance** | Every key and OAuth connection acts as a person and is Sandbox or Full; merging its own forks is a separate permission only an admin grants. Per-session change ledger with one-call rollback (merges included), and per-version provenance (human vs agent, which session, which key or connection). | | **Admin Copilot** | `/cm/copilot` — edit, create, and publish content in plain language, with RBAC and full audit logging. | | **llms.txt + JSON-LD** | Auto-generated `/llms.txt` and `/llms-full.txt` for AI crawlers, schema.org structured data on every page. | | **MCP for Readers** | Public read-only MCP endpoint at `/mcp-public` — visitors' agents can search and read the site natively. | | **Local Embeddings** | `LIGHTCMS_EMBEDDINGS_PROVIDER=ollama` for fully self-hosted semantic search — no API credits. | | **Self-Maintaining Sites** | Daily maintenance scans surface stale pages, missing meta, and broken links as an agent-ready work queue. | ## What's New in v6.0 | Feature | Summary | |---------|---------| | **Content Approvals** | Contributors submit content for approval; editors/admins approve or reject from `/cm/approvals`. Rejection comments auto-post to the discussion thread. | | **Contributor Role** | New RBAC role between Viewer and Editor. Can create content + upload assets (pending), post comments, and submit for approval — but cannot publish directly or manage system settings. | | **Approval Workflows** | Configurable trigger-based workflows (contributor, folder path, template ID, or tag). Sequential or concurrent mode with configurable approver lists. | | **Content Discussion** | Inline comment thread at the bottom of every edit page. @mention autocomplete, live badge count, admin delete. | | **Tabbed Bottom Panel** | Discussion, Version History, and Forks organized into tabs on the content edit page. | | **Approvals Dashboard** | My Queue + Other Pending + Workflow Config at `/cm/approvals`. Sidebar badge shows pending count. | | **Dashboard Sections** | Requiring Approvals and Recent Comments appear on the admin dashboard when relevant. | | **New Webhook Events** | `comment.created`, `content.pending_approval`, `asset.pending_review`. | | **14 new MCP tools** | Full comment and approval lifecycle — list/create/delete comments, full workflow CRUD, list/get/submit/approve/reject/cancel approval requests. | ## What's New in v5.0 | Feature | Summary | |---------|---------| | **Import Pipeline** | Three new import types — RSS/Atom feeds, Markdown/ZIP upload, CSV bulk import — with a unified job dashboard at `/cm/imports`. | | **RSS/Atom Import** | Configure recurring feed sources with hourly/daily/weekly schedules, template mapping, folder targeting, and auto-publish. | | **Markdown Import** | Upload `.md` files or `.zip` archives with YAML frontmatter. Supports Notion exports, Obsidian vaults, Hugo/Jekyll migrations, and AI-generated content. | | **CSV Import** | Upload CSV files and map columns to content fields. Specify the title column; all other columns become fields automatically. | | **Real-time Job Status** | SSE-powered live log stream at `/cm/imports/{jobID}`. Watch imports happen line-by-line or review full history after the fact. | | **10 new MCP import tools** | `list_import_sources`, `create_import_source`, `update_import_source`, `delete_import_source`, `trigger_import_source`, `import_markdown`, `import_csv`, `list_import_jobs`, `get_import_job`, `cancel_import_job`. | | **Agentic bulk content creation** | `import_markdown` is designed for AI agents to generate and import large content batches in a single call. See [MCP.md](MCP.md). | | **Deduplication** | Imports match by `full_path` — re-importing the same slug updates rather than duplicates. | ## What's New in v4.5 | Feature | Summary | |---------|---------| | **Webhooks** | HMAC-SHA256 signed events for `publish`, `unpublish`, `delete`, `create`, `update`. Admin UI at `/cm/webhooks` with delivery history and a docs page. | | **Scheduled Publishing** | Set a future `publish_at` timestamp on any content item; a background scheduler auto-publishes at the right time. | | **Content Locking** | Advisory lock when editing (30-min expiry). Warning banner if another user is already editing. Admins can force-unlock. | | **Incremental Static Regeneration (ISR)** | Template layout changes regenerate affected pages in 20-page batches, preventing server overload on large sites. | | **Edge Caching Headers** | ETag, Cache-Control, Last-Modified, and Vary headers on all public pages. 304 Not Modified support. | | **Cloudflare Integration** | Configure Zone ID + API Token to auto-purge Cloudflare cache on publish/unpublish. | | **Structured JSON Logging** | All server logs emit structured JSON with `timestamp`, `level`, `message`, and context fields. | | **Rate Limit Dashboard** | New tab on the audit log page showing locked IPs, attempt counts, and a one-click clear button. | | **MCP Prompt Resources** | Three new MCP resources: `lightcms://site/structure`, `lightcms://content/recent`, `lightcms://theme/config`. | ## Why LightCMS? **Lightweight**: A clean, focused codebase that's easy to understand, modify, and extend. No bloated frameworks or complex abstractions. **AI-Native**: Built from the ground up for the AI era: - **MCP Integration**: Full Model Context Protocol server with 130 tools and 3 prompt resources for website management. Supports both local stdio and HTTP streamable transports — connect from Claude Code, Claude Desktop, or any MCP-compatible client. - **OAuth 2.1 for Remote Agents**: Sandboxed desktop apps like Claude's Cowork can securely connect over HTTP using OAuth 2.1 with PKCE. No embedded passwords: sign in once, choose what the agent may do, and it acts as you within that. - **Fork-Friendly**: Designed to be forked and customized by Claude Code. Ask Claude to add new content types, modify templates, or build custom features — the codebase is structured for AI-assisted development. - **Natural Language Website Management**: Skip the admin UI entirely. Create pages, manage assets, customize themes, and publish content through conversation. ## Features ### Content Management - **Template System**: Define reusable content structures with custom fields (text, richtext, image, date, select, markdown) - **Static Page Generation**: Fast page loads from pre-rendered HTML — no runtime templating overhead - **Content Versioning**: Full version history with diff comparison and one-click revert - **Soft Delete**: Recover deleted content with undelete functionality - **Content Tagging**: Tag any content item with one or more freeform labels, then query by tag across your site - **Snippets**: Named HTML template fragments used as reusable rendering units in dynamic queries - **`lc:query` Directives**: Embed live content queries directly in template layouts — at publish time they expand into rendered lists of matching pages - **Content Collections**: Auto-generated paginated listing pages filtered by category - **Folders & URL Organization**: Hierarchical content organization with clean URL paths - **Rich Text Editor**: Quill 2 for visual content editing (vendored in `static/admin/quill/`, served from the site itself) - **Regex Search & Replace**: Site-wide or scoped search-and-replace with RE2 regex support, capture groups, and mandatory preview step - **Bulk Operations**: Update or apply field operations across up to 100 pages in a single API call; export/transform/re-import pipelines - **Scheduled Publishing** (v4.5+): Set a future `publish_at` timestamp; a background scheduler auto-publishes at the right time - **Content Locking** (v4.5+): Advisory lock while editing (30-min expiry); warning banner if another user holds the lock; admins can force-unlock - **Incremental Static Regeneration** (v4.5+): Template layout changes regenerate pages in 20-page batches to prevent server overload on large sites ### Import Pipeline (v5.0+) - **RSS/Atom Feed Sources**: Configure recurring import sources with configurable schedule (hourly/daily/weekly), template mapping, folder targeting, and auto-publish. Manage sources and run history from `/cm/imports`. Feed URLs must be publicly reachable (private and loopback addresses are refused, v7.4.3). - **Markdown + ZIP Import**: Upload `.md` files or `.zip` archives of Markdown. YAML frontmatter in each file controls title, slug, folder, template, tags, and scheduled publish time. Supports Notion exports, Obsidian vaults, Hugo/Jekyll site migrations, and AI-generated content. - **CSV Bulk Import**: Upload a CSV and specify which column is the title; all other columns are stored as content fields automatically. - **Real-time SSE Job Status**: Live log stream at `/cm/imports/{jobID}` — watch imports happen line-by-line or review full history after the fact. - **MCP-first Design**: `import_markdown` is specifically designed for AI agents to generate and import large content batches in a single call, replacing dozens of `create_content` calls with one `import_markdown` + one `get_import_job`. See [MCP.md](MCP.md) for workflow examples. ### Content Forks (v4.0+) - **Fork Workspaces**: Create named staging workspaces where sets of page edits can be authored, previewed, and reviewed before going live - **Sparse Model**: Only edited pages live in a fork — unmodified pages fall through to live content automatically - **Fork Preview Mode**: Activate via a floating bar injected into the live site; a cookie routes all page requests through the fork so you see exactly how the site will look after merge - **Merge with Conflict Detection**: Admins merge forks into live content; if a live page was changed after the fork was created, the conflict is recorded (fork wins). New pages created in the fork are inserted into live on merge, keeping the publish state they had in the fork (normally draft) unless the merge is run with `publish_new` - **Merges Clean Up (v7.4+)**: A merge deletes the fork's page copies and keeps the fork record, with created/updated counts, as history. Forks merged before v7.4 can be cleaned with `purge_fork_copies` - **Fork Copies Stay Out of the Way (v7.4+)**: Fork copies are left out of content listings and search unless `include_forks` is set, and can never be published directly or in bulk — only merged - **Hold Flag (v7.4+)**: Mark any draft `hold` to block every way of publishing it (publish, bulk publish, scheduled publish, approval, fork merge) until the flag is cleared - **Full MCP Toolset**: 9 dedicated fork tools — `list_forks`, `create_fork`, `get_fork`, `fork_page`, `remove_fork_page`, `merge_fork`, `archive_fork`, `delete_fork`, `purge_fork_copies` ### Batch & Parallel Operations Designed for agents that prefer parallelized, high-throughput workflows over sequential single-item calls: - **`bulk_update_content`**: Update up to 100 pages in a single API call. Each item uses merge semantics — only the fields you specify are touched. Supports `dry_run` validation before committing, and `auto_republish` to re-publish all previously-published pages in the same call, eliminating a separate publish step - **`bulk_field_operation`**: Apply a single operation (`set`, `clear`, `prepend`, `append`, `wrap`) to a field across every matching page in one call. Scope by template, folder, category, or explicit ID list. Ideal for adding disclaimers, updating metadata, or clearing stale fields across a content type - **`publish_multiple`**: Publish a list of IDs — or all drafts at once with `publish_all_drafts: true` — in a single request instead of looping over `publish_content` - **`export_content`**: Dump full field data for a scoped set of pages as a structured JSON array. Designed for export → transform → re-import pipelines; pair with `bulk_update_content` for large-scale content migrations - **Scoped Search & Replace**: Both `scoped_search_replace_preview` and `scoped_search_replace_execute` accept scope filters (folder, template, category, IDs) so agents can target precise subsets rather than running site-wide operations - **Parallel-Safe Read API**: `list_content` with `include_data: true` returns full field values in one fetch; agents can fan out reads across multiple `list_content` / `get_content` calls concurrently and then batch-write with `bulk_update_content` Recommended agent pattern for large updates: `list_content` → transform in parallel → `bulk_update_content` (up to 50/call) → `publish_multiple`. ### AI Chat Widget (v4.2+) - **Embeddable Widget**: Add a floating AI chat bubble to any page with a single ` ``` #### Ranking Configuration Ranking weights are configurable in the admin panel under **Tools → End User Search → Search Ranking**. Defaults: title-match boost 0.20, nav-page boost 0.15, concept-template boost 0.05, video-path penalty −0.05. Configure your Voyage AI key under **Configuration** to enable semantic search. ## Project Structure ``` lightcms/ ├── cmd/ │ ├── server/main.go # HTTP server entry point │ ├── mcp/main.go # MCP server entry point │ ├── cli/main.go # CLI tool entry point │ └── resetpw/main.go # Password reset utility ├── config/ │ └── config.go # Configuration loading ├── internal/ │ ├── apiclient/ # Reusable HTTP client for REST API │ ├── auth/ # Authentication, RBAC permissions, session management │ ├── cli/ # CLI subcommands and output formatting │ ├── database/ # MongoDB connection & operations │ ├── handlers/ # HTTP handlers (admin UI + REST API) │ ├── mcp/ # MCP server and tool definitions │ ├── middleware/ # API auth middleware (API keys + OAuth) │ ├── models/ # Data models & default templates │ ├── oauth/ # OAuth 2.1 authorization server │ └── services/ # Business logic (content, search, users, audit, etc.) ├── static/ # CSS, JS, and uploaded files ├── content/ # Custom pages and generated HTML └── .goreleaser.yaml # Release configuration ``` ## Default Templates ### Blog Post Fields: title, excerpt, featured_image, content, author, tags ### Press Release Fields: headline, subheadline, dateline, release_date, body, boilerplate, contact_info ### Explanatory Page Fields: title, subtitle, hero_image, intro, main_content, sidebar, cta_text, cta_link ### Concept Page Fields: title, definition, topic_links — ideal for wiki-style knowledge base entries ### Standard Page, Blank Page, Homepage General-purpose layouts for flexible content. ## Multi-User Access Control LightCMS v2.0+ supports multiple users with role-based permissions. ### Roles | Role | Capabilities | |------|-------------| | **admin** | Full access: manage users, templates, theme, settings, audit log, all API keys | | **editor** | Create/edit/delete/publish content; upload and delete assets; manage own API keys | | **viewer** | Read-only access to content, templates, assets, and settings | ### Audit Log Every mutation (content create/update/delete/publish, user management, settings changes, logins) is logged with the acting user's email, timestamp, and relevant details. Logs are retained for 365 days and accessible at `/cm/audit`. ### API Key Permissions API keys created by a user inherit that user's role. A key created by an editor cannot perform admin-only operations even if its token is shared. Admins can manage all keys; non-admins can only manage their own. ### First-Time Migration On first startup with an empty `users` collection, LightCMS automatically creates an admin user from the existing password hash in the database. Set the `LIGHTCMS_ADMIN_EMAIL` environment variable to specify which email address to use (defaults to `admin@localhost`). ## API Keys API keys are required for the REST API, MCP server, and CLI tool. Create them from the admin panel. 1. Log in at `/cm` 2. Go to **Settings** → **API Keys** 3. Click **Create New Key**, give it a name and description 4. Copy the key immediately — it's only shown once Keys use the format `lc_` followed by 32 hex characters. They're stored as SHA-256 hashes and inherit the permissions of the creating user. ## OAuth 2.1 Authorization LightCMS implements OAuth 2.1 so that remote MCP clients (like Claude's Cowork) can securely connect without embedding passwords or API keys. This follows the standard authorization code flow with PKCE. ### Endpoints | Endpoint | Purpose | |----------|---------| | `POST /oauth/register` | Dynamic client registration (RFC 7591) | | `GET /oauth/authorize` | Authorization page (sign-in + consent) | | `POST /oauth/token` | Token exchange and refresh | | `POST /oauth/revoke` | Token revocation (RFC 7009) | | `GET /oauth/jwks` | JWKS endpoint (opaque tokens, returns empty) | ### Security - **A connection acts as the person who approved it** (v7.5): every request loads that person's current role, so a role change, a disabled account or a disconnected app takes effect at once. The person also chooses the connection's access on the consent page: **Sandbox** (the default; it can change only forks it creates) or **Full** (anything their role can do). An admin can also let it merge its own forks. - **Tokens are for this server's MCP endpoint only** (RFC 8707): `/mcp` accepts them, `/api/v1` does not, and an MCP session stays bound to the connection that opened it. - **PKCE (S256)** required for all authorization requests - **Single use**: authorization codes and refresh tokens are consumed atomically; a refresh token that comes back after it was used ends the connection - **Short-lived access tokens**: 1-hour TTL - **Refresh tokens**: 30-day TTL - **Revocation**: `POST /oauth/revoke` (client credentials required) ends a token, or with a refresh token the whole connection; people disconnect apps at **Connected Apps** (`/cm/connected-apps`) - **Registration**: rate limited per address; redirect URIs must be `https`, or `http` to this device (loopback) - **Rate limiting**: failed login attempts trigger progressive lockouts (1 min → 5 min → 15 min) - **All tokens stored as SHA-256 hashes** in the database ### How Clients Connect 1. Client fetches `/.well-known/oauth-authorization-server` to discover endpoints 2. Client calls `POST /oauth/register` with its name and redirect URI 3. Client sends you to `/oauth/authorize` with a PKCE challenge 4. You sign in, see who the app will act as and where it sends you back, choose its access, and approve 5. Client exchanges the authorization code for access + refresh tokens 6. Client uses the access token as a Bearer token on `/mcp` This is all handled automatically by MCP-compatible clients — you just provide your LightCMS URL and approve the connection. ## REST API LightCMS provides a full REST API at `/api/v1/` authenticated with API keys. All endpoints enforce RBAC — the permissions of the key's owner, limited by the key's access, determine what's allowed. (OAuth access tokens are issued for the MCP endpoint and are not accepted here.) ### Authentication Include an API key in the `Authorization` header: ```bash curl -H "Authorization: Bearer lc_your_key_here" http://localhost:8082/api/v1/content ``` A key set to **Sandbox** access (the default for keys created in the admin UI) can read the site and change only forks it creates: every other write is refused with `403`. `GET/POST /api/v1/sandbox` and `POST /api/v1/sandbox/end` open and close a sandbox fork for the key. ### Endpoints | Resource | Endpoints | |----------|-----------| | Content | `GET/POST /content`, `GET/PUT/DELETE /content/{id}`, `POST .../publish`, `.../unpublish`, `.../restore`, `GET .../versions`, `POST .../versions/{v}/revert`, `GET /content/by-path?path=...` | | Templates | `GET/POST /templates`, `GET/PUT/DELETE /templates/{id}` | | Snippets | `GET/POST /snippets`, `GET/PUT/DELETE /snippets/{id}` | | Assets | `GET/POST /assets`, `GET/DELETE /assets/{id}`, `GET /assets/folders`, `GET /assets/by-path?path=...` | | Theme | `GET/PUT /theme`, `GET /theme/versions`, `POST /theme/versions/{v}/revert` | | Config | `GET/PUT /config` | | Redirects | `GET/POST /redirects`, `GET/PUT/DELETE /redirects/{id}` | | Folders | `GET/POST /folders`, `GET/DELETE /folders/{id}` | | Collections | `GET/POST /collections`, `GET/PUT/DELETE /collections/{id}` | | Search | `GET /search?q=...`, `POST /search-replace/preview`, `POST /search-replace/execute` | | API Keys | `GET/POST /api-keys`, `DELETE /api-keys/{id}` | | Utility | `POST /regenerate` | All endpoints return JSON. PUT endpoints support partial updates (only include fields you want to change). ## CLI Tool The `lightcms` CLI provides command-line access to all content management operations. ### Installation ```bash # Build from source go build -o bin/lightcms ./cmd/cli # Or download a release binary from GitHub ``` ### Configuration ```bash export LIGHTCMS_URL=http://localhost:8082 export LIGHTCMS_API_KEY=lc_your_key_here ``` Or use flags: `--url` and `--api-key`. ### Commands ```bash lightcms content list # List all content lightcms content get # Get content by ID lightcms content create --template --title "My Post" --slug my-post --data '{"body":"Hello"}' lightcms content publish # Publish content lightcms content versions # Show version history lightcms template list # List templates lightcms asset upload --file logo.png --path /images/logo.png lightcms theme update --primary-color "#1a1a2e" lightcms search "search terms" # Search content lightcms api-key create --name "CI/CD" # Create API key lightcms --json content list # JSON output ``` Run `lightcms --help` for full usage. ## MCP Server (AI-Powered Content Management) [![lightcms MCP server](https://glama.ai/mcp/servers/jonradoff/lightcms/badges/card.svg)](https://glama.ai/mcp/servers/jonradoff/lightcms) LightCMS includes a full MCP (Model Context Protocol) server with 130 tools and 3 prompt resources for managing your entire website through AI agents. It supports two transport modes: - **Stdio** — for local tools like Claude Code - **HTTP Streamable** — for remote/sandboxed clients like Claude's Cowork, Claude Desktop, or any MCP-compatible app ### Option A: Local Setup (Claude Code via Stdio) Best for developers using Claude Code directly on the same machine. 1. Create an API key in the admin panel at `/cm` → Settings → API Keys 2. Run the setup script: ```bash export LIGHTCMS_API_KEY=lc_your_key_here ./setup-mcp.sh ``` Or register manually: ```bash go build -o bin/lightcms-mcp ./cmd/mcp claude mcp add --transport stdio lightcms-mcp \ -e LIGHTCMS_URL="http://localhost:8082" \ -e LIGHTCMS_API_KEY="lc_your_key_here" \ -- /path/to/lightcms/bin/lightcms-mcp ``` Restart Claude Code and run `/mcp` to verify. ### Option B: Remote Setup (Cowork / Claude Desktop via HTTP + OAuth) Best for sandboxed desktop apps that can't run local binaries. The HTTP MCP endpoint at `/mcp` supports OAuth 2.1 authorization — no API keys or passwords need to be embedded in the client. **How it works:** 1. The client discovers your LightCMS instance via well-known endpoints 2. It registers as an OAuth client (one-time, automatic) 3. You sign in in the browser and choose its access: **Sandbox** (default) or **Full** 4. The client receives short-lived access tokens and refreshes them automatically The connection acts as you, with your role, limited by the access you chose. See and change it, or disconnect it, at `/cm/connected-apps`. **To connect from a remote MCP client**, just provide your LightCMS URL (e.g., `https://yoursite.example.com`). The client handles the rest using standard OAuth 2.1 discovery. **Discovery endpoints:** | Endpoint | Purpose | |----------|---------| | `/.well-known/oauth-authorization-server` | OAuth server metadata (RFC 8414) | | `/.well-known/oauth-protected-resource` | Protected resource metadata (RFC 9728) | | `/.well-known/mcp/server-card.json` | MCP server card with tool schemas | ### Authentication The MCP HTTP endpoint accepts both authentication methods: - **API keys** (`lc_` prefix) — long-lived, created in admin panel - **OAuth 2.1 tokens** — short-lived, obtained through the authorization flow Both act as a person (the key's owner, or whoever approved the connection) and enforce that person's role, limited by the key's or connection's access. Each MCP session is bound to the credential that opened it, and its tools call the REST API in-process as that caller. ### Agent access: Sandbox, Full, and merging (v7.5) | Setting | What it allows | |---------|----------------| | **Sandbox** | Read the site; create forks and change only the forks this key or connection created; upload new images, PDFs, fonts and text files (nothing executable, nothing replaced). Everything else is refused by the server, whichever tool sends it. | | **Full** | Anything the owner's role allows. `start_agent_sandbox` still works as an opt-in safety net: while it is open the server holds the key or connection to its fork. | | **Can merge its own forks** | Off by default; only an admin can turn it on, for an account that can merge. Lets `merge_fork` merge forks this key or connection created, after reviewing them with `get_fork_diff`. Without it, a person merges at `/cm/forks/{id}`. | Every merge names the review it was approved from: `get_fork_diff` returns a `digest`, and `merge_fork` refuses if the fork or a live page it changes was edited since. Merges write a version for each page they change, so `rollback_agent_session` undoes them. ### Available Tools (130 total) + 3 Prompt Resources - **Content** (19): list, read, create, update, update by path, delete, restore, publish, unpublish, publish multiple, preview, versions (list + get), revert, bulk create, bulk update, bulk field operation, export, backlinks - **Templates** (5): create, read, update, delete, list - **Snippets** (5): create, read, update, delete, list - **Assets** (6): upload, upload from URL, read, delete, list files and folders - **Search** (7): full-text search, end-user search, search-and-replace (global + scoped, preview + execute), reindex embeddings - **Settings** (23): theme CRUD + versioning + pinning, site config, redirects, folders, collections, regenerate all content - **Forks** (9): list, create, get, fork page, remove page, merge, archive, delete, purge copies - **Import** (10, v5.0+): list/create/update/delete/trigger import sources, import markdown, import CSV, list/get/cancel import jobs - **Webhooks** (6, v4.5+): list, create, update, delete webhooks; regenerate secret; list deliveries - **Content Locking** (4, v4.5+): get lock, acquire lock, release lock, force-unlock - **Scheduled Publishing** (3, v4.5+): schedule publish, list scheduled, cancel scheduled - **Audit & Link Check** (3, v4.5+): list audit logs, start link check, get link check results - **Comments** (3, v6.0+): list comments, post comment, delete comment - **Approvals** (11, v6.0+): list/get/create/update/delete approval workflows; list/get/submit/approve/reject/cancel approval requests - **Agent Sandbox & Governance** (10, v7.0+): start/get/end agent sandbox, fork diff, agent session changes, session rollback, maintenance report, run maintenance scan, backfill published dates, repair fork damage - **IndexNow** (3, v7.2.3+): get status, enable/disable, submit - **SEO & AI** (3, v7.3+): get/update SEO settings, AI traffic - **Prompt Resources** (3, v4.5+): `lightcms://site/structure`, `lightcms://content/recent`, `lightcms://theme/config` For detailed API documentation, see [MCP.md](MCP.md). ### Environment Variables (Stdio Mode) - `LIGHTCMS_URL` — Server URL (default: `http://localhost:8082`) - `LIGHTCMS_API_KEY` — API key (required for stdio mode) ## MCP Examples These examples show how the MCP tools work together to manage a website through natural language. Each example lists the user prompt and the exact MCP tool calls that execute behind the scenes. ### Example 1: Create and Publish a Blog Post **Prompt:** "Create a blog post about AI agents and publish it" **Tool calls:** 1. `list_templates` — finds the Blog Post template and its ID 2. `create_content` — creates the post with template ID, title, slug, and field data: ```json { "template_id": "6971098ad0761968133b8e43", "title": "The Rise of AI Agents", "slug": "rise-of-ai-agents", "data": { "excerpt": "How autonomous AI agents are reshaping software development.", "content": "

AI agents represent a fundamental shift...

", "author": "Editorial Team" } } ``` 3. `publish_content` — makes it live; a static HTML page is generated at `/rise-of-ai-agents` ### Example 2: Update the Site Theme **Prompt:** "Change the site colors to a dark theme with blue accents" **Tool calls:** 1. `get_theme` — reads current theme settings (colors, fonts, header/footer HTML) 2. `update_theme` — applies the new palette: ```json { "primary_color": "#1a1a2e", "secondary_color": "#16213e", "accent_color": "#0f3460", "background_color": "#0a0a0a", "text_color": "#e0e0e0" } ``` All published pages are automatically regenerated with the new theme. ### Example 3: Search and Replace Across the Entire Site **Prompt:** "Replace 'Acme Corp' with 'Acme Industries' everywhere on the site" **Tool calls:** 1. `search_replace_preview` — shows affected pages without making changes: ```json { "search": "Acme Corp", "replace": "Acme Industries" } ``` Returns a list of content items, matched fields, and match counts. 2. `search_replace_execute` — applies the replacement after user confirmation. Each affected content item gets a new version for rollback capability. ### Example 4: Create a Custom Template **Prompt:** "Create a template for team member profiles with name, role, bio, and photo" **Tool calls:** 1. `create_template` — defines the structure and HTML layout: ```json { "name": "Team Member", "slug": "team-member", "fields": [ { "name": "role", "label": "Role", "type": "text", "required": true }, { "name": "photo", "label": "Photo", "type": "image", "required": false }, { "name": "bio", "label": "Biography", "type": "richtext", "required": true } ], "html_layout": "
\"{{.title}}\"

{{.title}}

{{.role}}

{{.bio}}
" } ``` The template is immediately available for creating content. ### Example 5: Upload an Image and Use It in Content **Prompt:** "Upload this logo and add it to the about page" **Tool calls:** 1. `upload_asset` — uploads the file as base64 with a serve path: ```json { "filename": "logo.png", "serve_path": "/images/logo.png", "data_base64": "iVBORw0KGgoAAAANSUhEUg..." } ``` The image is now accessible at `/images/logo.png`. 2. `get_content` — retrieves the about page by path to get its current data 3. `update_content` — adds the image reference to the page's content field ### Example 6: Set Up URL Redirects **Prompt:** "The old blog moved from /news to /blog — set up redirects for the old URLs" **Tool calls:** 1. `create_redirect` — creates a permanent redirect: ```json { "from_path": "/news", "to_path": "/blog", "status_code": 301, "description": "Blog section moved from /news to /blog" } ``` Visitors to `/news` are automatically redirected to `/blog`. Multiple redirects can be created in sequence for different paths. ### Example 7: Content Versioning and Rollback **Prompt:** "Show me the version history for the homepage, then revert to version 2" **Tool calls:** 1. `get_content` — retrieves the homepage by path (`/`) to get its ID 2. `get_content_versions` — returns the full version history with timestamps and comments: ``` Version 5 — "Updated hero section" — 2026-02-25 Version 4 — "Added testimonials" — 2026-02-20 Version 3 — "Redesigned layout" — 2026-02-15 Version 2 — "Initial content" — 2026-02-10 Version 1 — "Created page" — 2026-02-08 ``` 3. `revert_to_version` — rolls back to version 2, creating a new version 6 with the old data. The published page is regenerated immediately. ### Example 8: Organize Content with Folders **Prompt:** "Create a /docs section and move the API reference page into it" **Tool calls:** 1. `create_folder` — creates the URL path segment: ```json { "name": "Documentation", "slug": "docs" } ``` 2. `get_content` — retrieves the API reference page to get its ID 3. `update_content` — moves it into the new folder: ```json { "id": "...", "folder_path": "/docs" } ``` The page is now accessible at `/docs/api-reference` instead of `/api-reference`. ### Example 9: Build a Content Collection (Blog Index) **Prompt:** "Create a blog listing page that shows all blog posts sorted by newest first" **Tool calls:** 1. `create_collection` — defines the collection with category filter, sorting, and display templates: ```json { "name": "Blog", "slug": "blog", "category": "blog", "sort_field": "created_at", "sort_order": "desc", "items_per_page": 10, "item_template": "", "page_template": "

Blog

{{.Items}}{{.Pagination}}
" } ``` A paginated blog listing is now live at `/blog`, automatically including any content with category "blog". ### Example 10: Build a Dynamic Tagged Index Page **Prompt:** "Create a glossary index that automatically lists all my concept pages grouped by category, and keep it updated as I add new pages" **Tool calls:** 1. `create_snippet` — creates a reusable rendering template for each result: ```json { "name": "glossary-pill", "html": "{{.Title}}" } ``` 2. `create_template` — creates the index page template with `lc:query` directives embedded: ```json { "name": "Concepts Index", "slug": "concepts-index", "fields": [ { "name": "intro", "label": "Introduction", "type": "textarea" } ], "html_layout": "
\n

{{.title}}

\n{{if .intro}}

{{.intro}}

{{end}}\n\n

AI & Machine Intelligence

\n
\n\n
\n\n

Games & Interactive Experiences

\n
\n\n
\n
" } ``` 3. `create_content` — creates the index page using the new template: ```json { "template_id": "", "title": "Concepts Glossary", "slug": "glossary", "data": { "intro": "An index of all concepts, grouped by category." } } ``` 4. `update_content` — tags several existing concept pages (each call): ```json { "tags": ["AI & Machine Intelligence"] } ``` 5. `publish_content` — publishes the index page; the `lc:query` directives are resolved at this moment and the page is generated with all currently-tagged content already populated. From now on, every time a new concept page is published with a matching tag, the index page at `/glossary` is automatically regenerated — no further action needed. ### Example 11: Full-Text Search and Content Audit **Prompt:** "Find all pages that mention 'pricing' and show me which ones are still in draft" **Tool calls:** 1. `search_content` — performs a full-text search across all content fields: ```json { "query": "pricing", "search_type": "fulltext" } ``` Returns matching content items with their publish status, paths, and which fields matched: ``` Found 4 results for 'pricing': - "Pricing Plans" at /pricing — published — matched in: content - "Enterprise FAQ" at /enterprise-faq — published — matched in: content, sidebar - "New Pricing Draft" at /new-pricing — draft — matched in: title, content - "Q1 Press Release" at /press/q1-update — draft — matched in: body ``` The two draft items can then be reviewed, edited, and published as needed. ### Example 12: Bulk Content Migration **Prompt:** "Add a 'last_reviewed' field to every page in our /docs section and publish them all" **Tool calls:** 1. `list_content` — fetches all content under `/docs` with full field data in one call: ```json { "folder_path": "/docs", "include_data": true } ``` Returns IDs, titles, current field values, and publish status for all 34 pages. 2. *(parallel)* Agent fans out into batches of 50 and calls `bulk_update_content` concurrently: ```json { "updates": [ { "id": "abc123", "data": { "last_reviewed": "2026-03-24" } }, { "id": "def456", "data": { "last_reviewed": "2026-03-24" } } ], "version_comment": "Added last_reviewed field — Q1 2026 audit", "auto_republish": true } ``` `auto_republish: true` re-publishes every previously-published page immediately — no separate publish step needed. All 34 pages are updated and live in two parallel calls instead of 34 sequential ones. ### Example 13: Fork-Based Staged Redesign **Prompt:** "Redesign the homepage and /about page in a staging area so I can preview before publishing" **Tool calls:** 1. `create_fork` — creates a named staging workspace: ```json { "name": "Q2 Redesign", "description": "Homepage and About refresh" } ``` Returns a fork ID. The live site is completely unaffected. 2. `fork_page` — copies the homepage into the fork and applies edits: ```json { "fork_id": "fork_abc123", "content_id": "homepage_id", "data": { "hero_headline": "Build the web with AI", "hero_subtext": "..." } } ``` 3. `fork_page` — does the same for `/about`: ```json { "fork_id": "fork_abc123", "content_id": "about_id", "data": { "body": "

Updated company story...

" } } ``` A floating preview bar is injected into the live site — visiting it with the fork cookie active shows both pages exactly as they'll look after merge. 4. `merge_fork` — after approval, merges both fork pages into live content and regenerates their static HTML: ```json { "fork_id": "fork_abc123" } ``` Returns a summary of merged pages (counts plus `created_ids` and `updated_ids`) and any conflicts detected. Pages the fork adds are created as drafts unless you pass `"publish_new": true`; pages on hold always stay drafts. The fork's page copies are deleted once the merge succeeds. ### Example 14: Site-Wide Notice with Auto-Republish **Prompt:** "Prepend a deprecation notice to all pages in our /v1 docs section and republish them" **Tool calls:** 1. `bulk_field_operation` — appends the notice to a specific field across the entire folder in one call: ```json { "operation": "prepend", "field": "body", "value": "
⚠️ This page covers v1 (deprecated). See current docs.
\n\n", "folder_path": "/v1", "auto_republish": true, "version_comment": "Added v1 deprecation notice" } ``` Returns counts of updated and republished pages. The live site reflects the change immediately. If the notice ever needs to be removed, a single `scoped_search_replace_execute` with the exact notice HTML reverts all pages in one call. ### Working with Snippets ``` # List all snippets list_snippets # Create a callout snippet create_snippet { "name": "callout-warning", "description": "Warning callout box", "html": "
⚠️ {{.Title}}

{{.Body}}

" } # Use a snippet inline in content update_content { "id": "...", "data": { "body": "Here is important information:\n\n[[include:callout-warning]]\n\nContinued text..." } } ``` ### Content Tagging & Index Pages ``` # Tag a page at creation time create_content { "template_id": "...", "title": "Introduction to AI", "slug": "intro-to-ai", "tags": ["AI & Machine Intelligence", "Getting Started"], "data": { "body": "..." } } # Inline tagging via content body update_content { "id": "...", "data": { "body": "This page covers #machine-learning and #neural-networks." } } # Query directive in a template layout (for index pages) ``` ### Bulk Operations ``` # Step 1: Export all Concept Pages with specific fields export_content { "template_name": "Concept Page", "fields": ["definition", "layer_badge"] } # Step 2: Transform the data externally, then bulk update bulk_update_content { "updates": [ { "id": "abc123", "data": { "layer_badge": "" } }, { "id": "def456", "data": { "layer_badge": "" } } ], "version_comment": "Updated layer badges" } # Clear a field across all pages of a template bulk_field_operation { "operation": "clear", "field": "old_badge", "template_name": "Concept Page", "version_comment": "Cleared deprecated field" } # Prepend a disclaimer to all blog posts bulk_field_operation { "operation": "prepend", "field": "body", "value": "
Views are my own.
", "template_name": "Blog Post", "version_comment": "Added disclaimer to all posts" } ``` ### Regex Search & Replace ``` # Preview: find all pages with old badge HTML pattern scoped_search_replace_preview { "search": "
", "replace": "", "regex": true, "template_name": "Concept Page" } # Execute after confirming preview scoped_search_replace_execute { "search": "
", "replace": "", "regex": true, "template_name": "Concept Page", "version_comment": "Removed old v1 badge HTML" } ``` ### Wikilinks ``` # Link to another page by title update_content { "id": "...", "data": { "body": "See also: [[Machine Learning]] and [[AI Ethics|ethics considerations]]." } } # Find what pages link to a given path get_backlinks { "path": "/concepts/machine-learning" } ``` ### Content Versioning ``` # See version history for a page get_content_versions { "content_id": "abc123" } # Restore a previous version revert_to_version { "content_id": "abc123", "version": 3, "version_comment": "Reverted to pre-redesign version" } ``` ## Development ```bash # Run with hot reload (using air) go install github.com/cosmtrek/air@latest air # Build all binaries go build -o bin/lightcms-server ./cmd/server go build -o bin/lightcms-mcp ./cmd/mcp go build -o bin/lightcms ./cmd/cli # Run the server ./bin/lightcms-server ``` ## Security Notes For production: 1. Use a strong `session_secret` — **minimum 32 characters** (generate with `openssl rand -hex 32`). The server hard-fails on startup if this requirement isn't met in production. 2. Set `LIGHTCMS_ADMIN_EMAIL` so the initial admin account uses your real email 3. Change the default admin password immediately after first login 4. Use HTTPS (put behind a reverse proxy like nginx or caddy) 5. Restrict MongoDB Atlas IP whitelist to your server IPs 6. API keys inherit the permissions of their owning user — keep admin keys secure 7. Review the audit log regularly at `/cm/audit` 8. Regularly backup your MongoDB database 9. Configure `max_upload_bytes` in site settings to cap file upload size for your use case Security features built in: - CSRF protection on all `/cm` routes - Admin Content-Security-Policy: scripts and stylesheets load from the site itself only (the editor's Quill files are vendored, not fetched from a CDN) - RBAC permission checks on all admin handlers and REST API endpoints - Session cookies: SameSite=Strict, 24-hour expiry, Secure in production - File uploads: extension whitelist + MIME validation + configurable size cap - API request body cap: 10 MiB enforced on all `/api/v1/` endpoints - Login rate limiting: escalating lockouts (1 min → 5 min → 15 min) - Per-endpoint rate limiters: regenerate (2/min), search-replace execute (10/min), bulk-update (5/min), export (5/min), reindex (1/min) - Passwords: bcrypt with cost=12 - Audit logging on all mutations with 365-day retention - Fly.io deployments: `Fly-Client-IP` header used for real client IP (unspoofable, unlike `X-Forwarded-For`) - Chat widget prompt injection defense: user input wrapped in XML delimiters, `