Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
MCP_EMAILNoLogin email (no token)
MCP_TOKENNoToken from Settings, MCP
DATABASE_URLYesPaperboy Postgres URL
MCP_PASSWORDNoPassword for MCP_EMAIL
PAPERBOY_SITENoDefault site slug or id

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
list_treeA

List ONE level of the page tree: the direct children of parentId, or the top-level pages when it is omitted, in the order the container sorts them, the whole level in one call. Returns an array of nodes, each with documentId, type, name, per-locale status and hasChildren — call list_tree again with a node's documentId to go deeper. Trashed pages are left out, and a page without children (or a parentId that matches nothing) gives [] rather than an error, so check hasChildren first. Use it to find the parentId for create_content/move_content. For every page at once use list_content {kind:'page'}; shared blocks and globals are not in the tree (list_content with kind block or global). Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

get_contentA

Read one content item for editing: its working version (the draft if one exists, else the published version) in one locale, with name, slug, status, revision and the full data field map. Read this before update_content so you merge into current values, and pass its revision when replacing with merge:false. data maps each field name to its value in the shape list_content_types reports (valueFormat). An unknown documentId, or one from another site, is refused as not found (pass site for another site); omit locale for the item's own language. This is the editing view; for the public view (published only, private fields removed) use delivery_get, in the delivery toolset. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

create_contentA

Create a new content item (page, block or global) as an unpublished draft, filled in ONE call: pass its fields in data (shapes from list_content_types {name}). Without data you get an empty shell that stays blank until update_content/set_field fills it. A page's position decides its URL and the template that renders it, so find the right parentId with list_tree (blog posts go under their blog page); omit it only for top-level sections. Under a list page, omit type to inherit the type it lists — a mismatch is refused, since the item would never appear on that list. Returns the new item in the get_content shape (documentId, locale, revision, data…), plus a hint when it still needs filling or placing. Then publish_content. To copy an existing item instead, use duplicate_content. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).

update_contentA

Save fields into a content item's working draft; nothing goes live until publish_content. Each value's shape follows its field type (list_content_types {name} gives valueFormat): text/markdown → string; richtext → TipTap doc {type:'doc',content:[…]}; contentArea → array of block instances; reference → {documentId}; image → asset documentId. data is MERGED over the draft by default, so omitted fields are kept; merge:false replaces the whole map (send every required field, and the get_content revision so a concurrent human edit is refused with 409 instead of overwritten). For one LONG text/markdown value use set_field: nested long strings can be mangled in transit. A value of the wrong shape is refused with the field, the expected shape and an example; text in another language than the branch is refused unless allowLanguageMismatch. An agent's edit marks the draft for review and cancels a pending scheduled publish of it. Returns the saved draft in the get_content shape, with its new revision. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

set_fieldA

Write ONE field of an item's draft as a plain string; the other fields are kept. It is the safest way to send long text or Markdown, because a flat string survives client serialization that can mangle long strings nested in update_content's data. field is a text, markdown, select, datetime or image field from list_content_types {name} (an image takes an asset documentId), or name for the item's display name; value is the field's whole new value. For richtext, contentArea or reference fields, or several fields at once, use update_content. A value the field can't take is refused with the expected shape and an example; text in another language than the locale branch is refused unless allowLanguageMismatch. Like any agent edit, it cancels a pending scheduled publish. Returns the saved draft in the get_content shape; publish_content separately. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

publish_contentA

Make a content item's working draft live in one locale (a LANGUAGE BRANCH: Norwegian text published into 'en' lands on the English site, so a clear mismatch is refused). A future publishAt SCHEDULES it instead: it stays invisible until then; call again with a new publishAt to reschedule, or without one to go live now. expireAt unpublishes it automatically later. Never fake a schedule with a date field — that publishes now. A changed slug or position gets a 301 from the old URL. Fires content.published. Refused when there is nothing new to publish, when required fields are empty (the error names them), or, if the site requires human review of agent drafts, until an editor approves it. Returns the item in the get_content shape with its new status. To take it down use unpublish_content; to drop unpublished edits, discard_draft.

copy_variantA

Copy a document's working version (name, slug and EVERY field) from one locale to another, server-side and atomically. USE THIS to move content that landed in the wrong language branch — re-typing it field by field loses fields. It REPLACES the target locale's draft (its unpublished edits are lost; its published version stays live until you publish_content the copy). The text is copied as-is: to translate it afterwards, run assist_writing {task:'translate'} and save with set_field. Returns the target locale's new draft in the get_content shape. To copy into a NEW item instead, use duplicate_content. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

unpublish_contentA

Take a content item off the live site in one locale: delivery stops serving it there (its URL starts answering 404) and content areas that reference it drop it. Fires the content.unpublished webhook. The content and its version history are kept, so publish_content brings it back. Name the item by documentId and the branch by locale (omit it for the item's own language); other locales are unaffected. Returns the item in the get_content shape, now unpublished in that locale. To remove the item entirely use trash_content. Requires the content.publish permission (Admin, Editor).

discard_draftA

Throw away the unpublished draft changes of one item (documentId) in one locale (omitted: the item's own language), so the working version falls back to the published one. The live site is unchanged; the discarded draft cannot be recovered. Refused for content that was never published (there is nothing to fall back to) — use trash_content to remove that instead. To roll back to an older version rather than the live one, use restore_version. Returns {ok:true}. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

move_contentA

Move a page in the page tree: parentId re-parents it (its URL path follows its new position, and the old URL gets a 301 automatically), beforeId/afterId place it among its siblings. Its whole subtree moves with it. Refused when the new parent is the page itself, is not a page, or lies outside your sections. Sibling order only sticks under a parent with a manual child sort; a computed sort (name, date…) re-orders it. Returns {ok:true}. To change only the URL's last segment, set a new slug with update_content instead; blocks and globals are not in the tree. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

duplicate_contentA

Copy a content item into a NEW unpublished document beside the original (same parent), named " (copy)", with every field of each locale's working version. A copied page gets a fresh slug, so it never collides with the original's URL. Children are not copied. locale only picks which language of the copy comes back. Returns the new item in the get_content shape; edit it, then publish_content. To copy one language into another locale of the SAME item use copy_variant; to start from nothing use create_content. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).

trash_contentA

Move a content item (by documentId) AND its whole page subtree to the trash: every locale is unpublished at once and disappears from delivery, so there is no locale to pass. Recoverable — restore_content brings it back (as drafts; publish again explicitly). An unknown documentId is refused as not found. Returns {trashed: }. To only take it off the live site, use unpublish_content instead. Requires the content.delete permission (Admin, Editor).

restore_contentA

Bring a trashed item back to its old place in the tree, together with the descendants that were trashed with it. Restored content is NOT republished — call publish_content for each locale that should go live. Refused if its parent is still in the trash (restore that first) or a live sibling took its URL meanwhile. There is no locale to pass: every language comes back. Returns {restored: }. Find trashed ids with list_content {kind:'trash'}. To roll back an item that is not trashed, use restore_version instead. Requires the content.delete permission (Admin, Editor).

list_versionsA

List a content item's version history in one locale, newest first and complete (no paging); locale picks the language branch (omitted: the item's own). Each version's id, versionNumber, status, whether it is the live one, name, and who created it when. An unknown documentId is refused as not found. Use it to find what to roll back to — pass a version's id (not its versionNumber) to restore_version. For the current draft itself, use get_content. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

restore_versionA

Roll back an item to an older version: copy that version's name, slug and fields into the working draft of one locale. versionId is the version's id from list_versions of the SAME locale, not its versionNumber. It REPLACES the current draft of that locale, so unpublished edits there are lost; published versions stay in the history (a rollback can itself be rolled back), and nothing goes live until you publish_content. Returns the new draft in the get_content shape. To bring back a TRASHED item use restore_content; to simply drop unpublished edits use discard_draft. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

list_contentA

List EVERY item of one kind in this site that you can see, as one flat array (documentId, name, type, per-locale status, plus parentId for pages and folderId for blocks) — no tree walking needed. kind 'page': pages, to find one by name or pick a parent/reference target (list_tree shows one level in display order). kind 'block': SHARED blocks — reusable block documents (banners, forms, CTAs…) that pages place by reference, so one edit updates every page using them; inline blocks live inside a page's data and are not listed. kind 'global': singletons (header, footer, site settings…), which are neither in the page tree nor blocks, so this is the only way to enumerate them. kind 'trash': items removed with trash_content (any kind), newest first, with deletedAt; nothing is purged automatically, and restore_content brings one back. Everything comes back in one call (no paging). Edit any of them with get_content/update_content on its documentId. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

list_content_typesA

Read the content model (instance-wide, shared by all sites). Pass name to get ONE type's definition — name, kind, displayName, schemaType and fields (name, type, required, options, allowed blocks of a content area…); an unknown name is refused with the list of real names. Call this before writing content of a type. Each field carries valueFormat + valueExample: the exact JSON shape create_content/update_content expect. These are LIVE types editors fill in; reusable type recipes (templates) live in the schema toolset. Omit name to get every full definition, which is large, so prefer name. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

list_assetsA

List this site's whole media library (images and PDFs), newest first, in one call (no paging, so a big library is a big response): each asset's documentId, filename, mime, size, public url, alt text, attribution for stock imports, and folder. Media is per site: pass site to see another site's library. An empty library gives []. Use it to reuse an existing image: an image field stores the asset's documentId, set with set_field. To add a new file use upload_asset (URL, base64 or stock photo) or create_upload_link (files on your machine). Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

update_asset_altA

Set (overwrite) an image asset's alt text — the description screen readers announce and search engines index. Alt text belongs to the ASSET, not to a page: every page showing the image picks up the change, and set_field/update_content cannot set it. documentId is the asset's (from list_assets or an upload), not a page's; an unknown one is refused as not found. Describe what the image shows in one sentence; use "" only for a purely decorative image. Returns the updated asset with its new alt. assist_writing cannot write alt text (it needs the image itself), so look at the image and write it. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

delete_assetA

Permanently delete a media asset — by the asset's documentId from list_assets, not a page's — removing the database row and the stored file. Irreversible. Content that still points at it is not blocked: delivery resolves those fields to null, so the image silently disappears from those pages — clear or replace them first. To stop using an image on one page, just clear that field with set_field and keep the asset. Returns {ok:true}. Requires the content.delete permission (Admin, Editor).

search_stock_imagesA

Search the configured stock photo provider (Settings → Stock images; Unsplash) for photos to use on a page. Returns up to 24 candidates, best match first, each with id, description and attribution; nothing is stored yet. To USE one: upload_asset {stockImageId: } imports it into the media library, then set_field the returned asset documentId on an image field. Refused with a hint when no provider key is configured. To reuse a photo you already have, list_assets. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

create_upload_linkA

Get a 15-minute link for uploading LOCAL files (images, PDFs) into the media library — the way to upload files on your own machine, which this server can't read. POST them as multipart/form-data with the returned header, e.g. with curl (several -F file=@… per request is fine). The response lists each stored asset's documentId: set it on an image field with set_field, and give it alt text with update_asset_alt. The link uploads as you, into this site (pass site to upload into another), and expires after 15 minutes. Returns {uploadUrl, headers, expiresAt, example, limits}: PNG, JPEG, GIF, WEBP or PDF, max 5 MB per file, up to 20 files per request. Refused when the server has no PUBLIC_URL configured — then use upload_asset with dataBase64. For a file at a public URL, or a stock photo, use upload_asset instead. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).

upload_assetA

Add a file (PNG, JPEG, GIF, WEBP or PDF, max 5 MB) to the media library from exactly one source: a public URL, base64 bytes, or a stock photo id from search_stock_images (stored with its attribution). Returns the new asset — set its documentId on an image field with set_field. Each call stores a NEW asset, even for a file already in the library (reuse one via list_assets). A URL must be publicly reachable: private-network addresses are refused at every redirect hop, and a download over 5 MB is cut off with an error. For files on YOUR machine prefer create_upload_link: base64 through a tool call costs roughly 1 token per 2-3 bytes. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).

list_sitesA

Report where this server acts and in which languages. Returns: sites (every site you can reach: id, slug, name), activeSiteId, tokenScopedTo (the one site your token is limited to, or null), activeSite (startPageId — the page served at /, or null — and previewBaseUrl, where the admin previews a page as {previewBaseUrl}/{locale}{urlPath}), and locales (enabled language branches in order: code, displayName, isDefault, fallback). It lists no content (use list_tree or list_content for that). Use it to pick the locale matching the language you write, so text lands in the right branch, and a slug to pass as site to other tools (pass site here too to see that site's setup). Content in another site is invisible to every tool, so an empty tree or search usually means the wrong site, not missing content. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).

assist_writingA

Run the CMS's writing assistant on a piece of text: SEO titles and descriptions, summaries, polishing, rewrites, variants, translation, drafting from a brief, schema.org field suggestions. Returns {result, provider}: result is the text (for variants, a JSON array of 3 strings). It saves nothing — write the result with set_field or update_content. It uses the model set in Settings → AI; without one, only meta_title, meta_description and summarize work (by truncation) and the rest are refused with a hint. It cannot write image alt text, since it never sees the image: write that yourself and save it with update_asset_alt. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A4.6/5.0

Scored across 25 tools

Disambiguation4/5

Most tools target distinct resources and actions (content vs assets vs versions), but a few pairs overlap: list_content vs list_tree both list pages, update_content vs set_field both edit drafts, and upload_asset vs create_upload_link both add assets. Descriptions explicitly differentiate when to use each, so misselection is unlikely but possible.

Naming Consistency5/5

All names use snake_case with a leading verb (list_, get_, create_, update_, etc.), and the noun patterns are predictable across content, asset, and version tools. Minor forms like assist_writing (verb + gerund) remain readable and do not break the overall convention.

Tool Count4/5

25 tools is on the heavy side for a single server, but the domain is broad—content lifecycle, assets, versions, sites, and AI writing—and each tool covers a distinct operation without obvious redundancy. Slightly over the typical 3–15 range, but justified for a full CMS API.

Completeness4/5

Core content lifecycle is thoroughly covered: create/read/update/publish/unpublish/trash/restore, versioning, locale variants, and asset upload/delete/alt text. Minor gaps exist—no direct asset file replacement, no text search for content, and no schema or site write operations—but these are either out of scope or workable via list operations.

Maintenance

ActivityActive
ResponsivenessNo recent community issues