Features
A complete list of features that plym MCP server exposes.
Updated Aug 04, 2026
On this page
The plym MCP server exposes six tools. This page lists each one's parameters, return fields, required role, and errors.
All six are write-once or read-only. There is no tool to update, delete, publish, or unpublish anything — a post created through MCP lands as a draft and a person finishes it in the admin. Set up the connection first in Client setup.
Tools
| Tool | Minimum role | Result |
|---|---|---|
create_post |
editor |
Creates a draft post |
upload_media |
editor |
Uploads an image, returns its URL |
list_posts |
editor |
Lists every post, drafts included |
list_users |
reader |
Lists every account with email and role |
get_from_url |
reader |
Returns the raw HTML at a URL |
md_from_html |
reader |
Converts an HTML string to markdown |
plym has three roles: reader, editor, and administrator. administrator satisfies every row above.
create_post
Takes a single post object. Creates the post with status draft — there is no parameter to publish it.
| Field | Type | Default | Description |
|---|---|---|---|
title |
string, 1–240 chars | required | Post title |
slug |
string, 1–240 chars | required | Must match ^[a-z0-9]+(?:-[a-z0-9]+)*$, and must be unique across every post and category on the instance |
content |
string | "" |
Markdown body |
excerpt |
string or null | null |
Summary used in listings and meta description |
cover |
string or null | null |
Image URL; pass the url returned by upload_media |
canonical_url |
string, max 2048 | null |
Must match ^https?://.+ |
weight |
integer or null | null |
Manual ordering value |
category_id |
integer or null | null |
Existing category id; null leaves the post uncategorised |
tags |
array of strings | [] |
Tag names, created if they do not exist |
faqs |
array of integers | [] |
Existing FAQ ids |
No tool returns category or FAQ ids. Read them from GET /api/categories and GET /api/faqs, or from the admin, before calling this tool.
The return value carries every field of the created post plus id, path, status, reading_time, author, category, tags, created_at, and updated_at. path is <category-slug>/<slug>, or just <slug> when uncategorised.
{
"id": 17,
"slug": "static-rendering-benchmarks",
"path": "engineering/static-rendering-benchmarks",
"title": "Static rendering benchmarks",
"status": "draft",
"reading_time": 4,
"weight": null,
"published_at": null,
"rendered_path": null,
"author": { "id": 3, "display_name": "Ada Okonjo", "avatar_url": null, "links": [] },
"category": { "id": 2, "name": "Engineering", "slug": "engineering", "weight": 10 },
"tags": [{ "id": 41, "name": "performance", "slug": "performance" }],
"faqs": []
}
upload_media
Pass url for an image plym should fetch, or data for base64-encoded file bytes. Passing both is an error, as is passing neither.
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string or null | null |
Publicly reachable image URL |
data |
base64 string or null | null |
File bytes |
filename |
string or null | null |
Name for a data upload; falls back to upload |
plym re-encodes every upload to WebP at quality 82 and stores it under a generated UUID filename. The original format and filename are not kept as the served file. The size limit is 10 MB by default, set by PLYM_UPLOAD_MAX_BYTES.
{
"id": 12,
"filename": "702e159035704dcd952792bd91ef31ee.webp",
"original_name": "benchmark-chart.png",
"mime_type": "image/webp",
"size_bytes": 48210,
"width": 1600,
"height": 900,
"url": "/blog/media/702e159035704dcd952792bd91ef31ee.webp",
"uploader_id": 3
}
Base64 of a real image runs to megabytes of tokens. Prefer url, or have the client shell out to a script that reads the file and makes the call.
list_posts
Takes no parameters. Returns every post on the instance in every status — draft, published, and archived — paging through the API 200 at a time until it has them all. There is no filter, search, or limit parameter.
Each item has id, slug, path, title, status, reading_time, excerpt, cover, canonical_url, weight, published_at, author, category, tags, created_at, and updated_at. Post bodies are not included; there is no tool that returns the content of an existing post.
This tool requires editor because it always asks for drafts.
list_users
Takes no parameters. Returns every account with id, email, role, is_active, display_name, bio, avatar_url, links, created_at, and updated_at.
Any signed-in account can call this, including a reader. Every client you connect can read every user's email address and role.
get_from_url
Takes url and returns the response body as text, untouched and untruncated. Redirects are followed and the request times out after 30 seconds.
The fetch runs from inside the MCP container, not from your client. It can therefore reach private addresses and other containers on your Docker network that the client cannot — including your own api on http://api:8000. Treat the tool as an unrestricted server-side fetcher when deciding who gets an account.
md_from_html
Takes html and returns markdown, with leading and trailing whitespace stripped. The conversion is local; no network request is made and nothing on your instance is touched.
Input: <h1>Benchmarks</h1><p>Rendered in <b>1.2s</b></p>
Output: Benchmarks
==========
Rendered in **1.2s**
Errors
A failed call returns isError: true with the message as text content.
| Message | Cause | Action |
|---|---|---|
Missing credentials: set the X-User-Identity and X-Mcp-Token headers. |
One or both headers absent | Add both headers to the client config |
X-User-Identity is not a valid email address. |
The header value is not an email address | Send the account's email address |
Authentication failed: check your email and password. |
plym rejected the pair | Fix the password; X-Mcp-Token takes the account password |
This account is not allowed to list this resource. |
list_posts or list_users refused |
Give the account the editor role |
This account cannot create posts (editor role required). |
create_post refused |
Give the account the editor role |
This account cannot upload media (editor role required). |
upload_media refused |
Give the account the editor role |
Pass eitherurlordata, not both. |
Both image inputs given | Send one |
Passurlfor a remote image, ordatawith base64-encoded file bytes. |
Neither image input given | Send one |
Unsupported image format: the bytes are not a readable image. |
plym could not decode the upload | Convert to PNG, JPEG, or WebP first |
File is too large for this plym instance's upload limit. |
Upload over PLYM_UPLOAD_MAX_BYTES |
Shrink the image or raise the limit |
Client error '409 Conflict' for url '.../api/posts' |
The slug is taken by a post or a category | Choose another slug |
Client error '404 Not Found' for url '.../api/posts' |
category_id does not exist |
Read the real ids from GET /api/categories |
String should match pattern '^[a-z0-9]+(?:-[a-z0-9]+)*$' |
Slug rejected before the request left the client | Use lowercase words joined by single hyphens |
The two Client error rows are raw HTTP failures passed through with the status code and no plym-specific wording.
Not available
- Updating, deleting, publishing, unpublishing, or re-rendering a post.
- Reading the body of an existing post.
- Creating or listing categories, tags, FAQs, or users.
- Reading or writing instance config.
- Listing or deleting uploaded media.
Use the REST API or the admin for all of the above.