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.
A Model Context Protocol (MCP) server for integrating MCP clients with Plone CMS via REST API. Enables content management, search, workflow operations, and Volto blocks management.
brew install node on macOS or from nodejs.org)npm install -g pnpm or brew install pnpm on macOS)git clone https://github.com/plone/plone-mcp.git
cd plone-mcp
pnpm install
pnpm run buildAdd 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": "node",
"args": ["/absolute/path/to/plone-mcp/dist/index.js"],
"env": {
"PLONE_BASE_URL": "https://demo.plone.org",
"PLONE_USERNAME": "admin",
"PLONE_PASSWORD": "admin"
}
}
}
}Without environment variables:
{
"mcpServers": {
"plone": {
"command": "node",
"args": ["/absolute/path/to/plone-mcp/dist/index.js"]
}
}
}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.
| 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_search | Search content | plone_search({query: "news", portal_type: ["Document"]}) |
plone_transition_workflow | Change workflow state | plone_transition_workflow({path: "/page", transition: "publish"}) |
// 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"
})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.
# Development mode with hot reload
pnpm run dev
# Test with MCP Inspector
pnpm run inspector
# Build for production
pnpm run build| Issue | Solution |
|---|---|
| "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 build | Run pnpm install to ensure all dependencies are installed |
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.