Plone Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Plone Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Talk to your Plone website instead of clicking through it. Plone MCP lets AI assistants like Claude create and edit pages, publish content, search the site, and manage translations on your behalf, in plain language - no coding required to use it, and nothing to change on your Plone site to enable it.
It's built on the Model Context Protocol (MCP), an open standard that lets AI assistants safely connect to external tools and data. Plone MCP exposes Plone's REST API as a set of MCP tools, so any MCP-compatible client - Claude Desktop, Claude Code, and others - can drive your site, and developers can script, automate, or build on top of the same tools.
Requires Node.js 22+. Add this to Claude Desktop's config file, then restart Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json{
"mcpServers": {
"plone": {
"command": "npx",
"args": ["-y", "@plone/mcp"]
}
}
}Now ask Claude to connect to your Plone site, e.g. "Connect to https://demo.plone.org as admin/admin".
^20.19.0 || >=22.12.0; install: brew install node on macOS or from nodejs.org)pnpmis only needed if you want to clone the repo and develop locally (see Local Development). The recommended setup below usesnpxand doesn't require cloning anything.
The server ships two entry points:
plone-mcp bin / dist/stdio-server.js) - for local MCP clients such as Claude Desktop.dist/http-server.js) - a streamable-HTTP server with per-session state, listening on PORT (default 3001) at /mcp. Start it with make start.The @plone/mcp package is published on npm, so there's nothing to install or build - npx fetches and runs it on demand.
Add to Claude's configuration file:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonWith environment variables (optional):
{
"mcpServers": {
"plone": {
"command": "npx",
"args": ["-y", "@plone/mcp"],
"env": {
"PLONE_BASE_URL": "https://demo.plone.org",
"PLONE_USERNAME": "admin",
"PLONE_PASSWORD": "admin"
}
}
}
}Without environment variables (useful if you connect to different Plone sites and prefer to pass credentials per session via plone_configure):
{
"mcpServers": {
"plone": {
"command": "npx",
"args": ["-y", "@plone/mcp"]
}
}
}Call plone_configure once per session:
// Using environment variables
plone_configure({});
// OR providing credentials/token directly to the LLM
plone_configure({
baseUrl: "https://demo.plone.org",
username: "admin",
password: "admin",
});
plone_configure({
baseUrl: "https://demo.plone.org",
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
});Note: Arguments take precedence over environment variables.
Clone the repo if you want to modify the server, debug it, or run it with the MCP Inspector:
git clone https://github.com/plone/plone-mcp.git
cd plone-mcp
make install
make buildPoint Claude Desktop at your local build instead of the npx command:
{
"mcpServers": {
"plone": {
"command": "node",
"args": ["/absolute/path/to/plone-mcp/dist/index.js"]
}
}
}Development commands:
# Install the dependencies
make install
# Build for production (compiles TypeScript and copies blocks.json)
make build
# Run the HTTP server / the STDIO server from the build
make start
make stdio
# Debug with the MCP Inspector
make inspector
# Tests (Vitest)
make test-all # everything
make test # unit tests only
make test-coverage # with coverage
# Static checks
make lint # ESLint over src/ and __tests__/
make format # ESLint with --fix
make type-check # tsc over sources and testsRun make help to list every available target.
| Tool | Description | Example |
|---|---|---|
plone_configure | Connect to Plone (call once per session) | plone_configure({baseUrl, username, password}) or plone_configure({}) for env vars |
plone_get_content | Get content by path | plone_get_content({path: "/news"}) |
plone_create_content | Create new content | plone_create_content({parentPath: "/", type: "Document", title: "Page"}) |
plone_update_content | Update existing content | plone_update_content({path: "/page", title: "New Title"}) |
plone_delete_content | Delete content | plone_delete_content({path: "/old-page"}) |
plone_copy_content | Copy content into a container | plone_copy_content({parentPath: "/archive", source: "/news"}) |
plone_move_content | Move content into a container | plone_move_content({parentPath: "/archive", source: "/old-page"}) |
plone_search | Search content | plone_search({query: "news", portal_type: ["Document"]}) |
plone_transition_workflow | Change workflow state | plone_transition_workflow({path: "/page", transition: "publish"}) |
plone_get_navigation_tree | Get hierarchical site structure | plone_get_navigation_tree({root_path: "/", depth: 2}) |
plone_get_translation | List translations of a content item | plone_get_translation({path: "/en/my-page"}) |
plone_link_translation | Link existing content as a translation | plone_link_translation({path: "/en/my-page", id: "/de/meine-seite"}) |
plone_unlink_translation | Remove a translation link | plone_unlink_translation({path: "/en/my-page", language: "de"}) |
plone_create_working_copy | Check out content into a working copy | plone_create_working_copy({path: "/my-document"}) |
plone_get_working_copy | Show working copy relationship and lock | plone_get_working_copy({path: "/my-document"}) |
plone_checkin_working_copy | Check in a working copy | plone_checkin_working_copy({path: "/working_copy_of_my-document"}) |
plone_cancel_working_copy | Discard a working copy | plone_cancel_working_copy({path: "/working_copy_of_my-document"}) |
// 1. Prepare blocks (60-second TTL - meant to be used inmediatly before content creation/editing)
plone_create_blocks_layout({
blocks: [
{
type: "text",
data: { text: "Welcome to our site!" },
},
{
type: "teaser",
data: {
href: "/about",
title: "Learn More",
description: "Discover what we do",
},
},
],
});
// 2. Create content (within 60 seconds), the previously prepared blocks will automatically be included in the request
plone_create_content({
parentPath: "/",
type: "Document",
title: "Homepage",
});// Add a single block
plone_add_single_block({
path: "/homepage",
blockType: "text",
blockData: { text: "New paragraph" },
position: 1,
});
// Update a block
plone_update_single_block({
path: "/homepage",
blockId: "51176ead-7b59-402d-9412-baed46821b36", // Get ID from plone_get_content
blockData: { text: "Updated text" },
});
// Remove a block
plone_remove_single_block({
path: "/homepage",
blockId: "51176ead-7b59-402d-9412-baed46821b36",
});Use plone_get_block_schemas() to see all block types and their properties.
// Configure connection
plone_configure({
baseUrl: "https://mysite.com",
username: "editor",
password: "secret",
});
// Create with blocks
plone_create_blocks_layout({
blocks: [{ type: "text", data: { text: "Article content..." } }],
});
plone_create_content({
parentPath: "/news",
type: "News Item",
title: "Breaking News",
});
// Publish
plone_transition_workflow({
path: "/news/breaking-news",
transition: "publish",
});// Copy one or more items into another folder (originals stay in place)
plone_copy_content({
parentPath: "/archive",
source: ["/news/breaking-news", "/news/annual-report"],
});
// Move an item, removing it from its current location
plone_move_content({
parentPath: "/archive",
source: "/old-page",
});plone_search({
query: "annual report",
portal_type: ["Document", "File"],
review_state: ["published"],
sort_on: "modified",
sort_order: "descending",
b_size: 10,
});⚠️ Prepared blocks expire after 60 seconds - Always call plone_create_blocks_layout immediately before creating/updating content.
⚠️ Configure once per session - Run plone_configure once at the start of each session before using other tools. Once configured, you can use all other tools without reconfiguring.
| Issue | Solution |
|---|---|
command not found when using npx | Make sure the args in your config use plone-mcp as the binary name (not plone-mcp-server) — this was renamed in the package. |
| "Plone client not configured" | Run plone_configure once at the start of your session |
| "Block not found" | Use plone_get_content to get valid block IDs |
| Connection errors | Verify Plone URL and credentials are correct |
| Blocks not applied | Call plone_create_blocks_layout immediately before create/update (60s TTL) |
| TypeScript errors during local build | Run make install to ensure all dependencies are installed |
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.