An experimental local/remote MCP server for discovering, validating, and indexing Gutenberg block schemas and markup.
Caution
Early-stage notice: This repository is an experimental fork/refactor built on the original author's work. It is useful for local development and evaluation, but APIs, storage details, validation coverage, and deployment guidance may change. Validate generated content in your own WordPress environment before using it in production.
Important
AI-assisted refactor: The changes in this fork — including the Docker deployment, the HTTP transport, the Git/source policy modules, the test suite, and the rewritten documentation — were produced with the help of AI coding agents operating under human review. Upstream code is attributed as required by the MIT License; see NOTICE.
The original project is maintained by its author at pluginslab/wp-blockmarkup-mcp. This repository is a separate fork/refactor by Zaher Ghaibeh; it is not a replacement for, or an official release of, the upstream project.
Fork base: this fork was created from upstream commit e490fcd (full SHA e490fcded2b910f79eeb0ed087773a96e0d6dd30, upstream v1.1.1, 2026-05-13). Code, documentation, or configuration in this repository that is not attributed to Zaher Ghaibeh originates from that commit or earlier. Newer upstream commits are not part of this fork's base — check the upstream repository directly for changes after that point.
The upstream project remains the reference for the original implementation and its history. This fork keeps that core idea—extract real block source code instead of asking an AI model to guess Gutenberg markup—and adds a local-first workflow, stronger source and runtime boundaries, and container deployment.
| Area | Original project | This fork/refactor |
|---|---|---|
| Distribution | npm-oriented examples | Clone the repository and run it locally; no npm package distribution is intended |
| Runtime | Primarily local MCP usage | Local stdio plus an authenticated, read-only streamable HTTP mode |
| Deployment | Local process | Docker image and Docker Compose deployment with persistent SQLite storage |
| Source credentials | Caller-selected token configuration | Server-owned WP_BLOCKMARKUP_GITHUB_TOKEN for private GitHub sources |
| Source safety | Basic repository configuration | Canonical github.com URL checks, safe ref validation, path-containment checks, and bounded Git execution |
| HTTP safety | Local integration focus | Bearer authentication, origin checks, body limits, rate limits, and rejection of batch requests |
| Indexing and validation | Core extraction and markup validation | Structured indexing outcomes (success, degraded, failure), capability-aware markup generation, and stricter Gutenberg delimiter/save-function validation |
| Maintenance | Upstream release flow | Hermetic tests and linting for the fork; changes are still experimental and may diverge from upstream |
The two repositories may evolve independently. Check both repositories before assuming that a feature, command, or database migration exists in the other one.
AI assistants often generate Gutenberg content from incomplete or stale training data. They can invent attributes, use the wrong CSS class conventions, ignore nesting rules, or emit static HTML for dynamic blocks. WordPress then reports invalid blocks and asks for recovery.
This fork and refactor also make the project practical to host online as a shared team service. Instead of every developer running a separate local copy, a team can deploy one authenticated, continuously indexed instance and use it from their MCP-compatible clients. Local-first execution remains useful for development and testing, but shared online hosting is an intended way to operate the project.
This server indexes the actual source code of WordPress core, WooCommerce, or another block-based plugin so an MCP-compatible assistant can:
- Search for relevant blocks.
- Read their attributes, supports, variations, and save patterns.
- Generate markup from verified examples.
- Validate markup before sending it to WordPress.
Indexed data includes block metadata, attribute schemas, support configurations, variations, UI mappings, save-function patterns, block type classification, and validation status.
This project is intentionally local-first. Clone it, install dependencies, and run the checked-out source:
git clone https://github.com/zaherg/wp-blockmarkup-mcp-server.git
cd wp-blockmarkup-mcp-server
npm ciNode.js 22, 24, or 26 is required. The repository is not intended to be installed from or published as an npm package.
Run the CLI from the checkout. The CLI's internal program name (used in --help output) is wp-blocks:
node src/cli.js source:add \
--name gutenberg \
--type github-public \
--repo https://github.com/WordPress/gutenberg \
--branch trunkOther source types:
# Public GitHub repository
node src/cli.js source:add \
--name woocommerce-blocks \
--type github-public \
--repo https://github.com/woocommerce/woocommerce \
--subfolder plugins/woocommerce-blocks \
--branch trunk
# Private GitHub repository. The token is owned by the server process.
export WP_BLOCKMARKUP_GITHUB_TOKEN=your-fine-grained-read-token
node src/cli.js source:add \
--name my-private-blocks \
--type github-private \
--repo https://github.com/your-org/your-blocks \
--branch main
# A local plugin under active development
node src/cli.js source:add \
--name my-local-blocks \
--type local-folder \
--path /absolute/path/to/wp-content/plugins/my-blocksUseful commands:
node src/cli.js source:list
node src/cli.js index --source gutenberg
node src/cli.js search "image gallery"
node src/cli.js schema core/paragraph
node src/cli.js validate '<!-- wp:paragraph --><p>Hello</p><!-- /wp:paragraph -->'
node src/cli.js stats
node src/cli.js rebuild-indexFor a client that starts MCP servers with a command, point it at the checked-out file. For example, a Claude Code-style configuration is:
{
"mcpServers": {
"wp-blockmarkup": {
"command": "node",
"args": ["/absolute/path/to/wp-blockmarkup-mcp-server/src/mcp-server.js"]
}
}
}The default stdio server exposes both read tools and local source-management tools. Keep the process on a trusted machine when using administrative operations. See CLI vs remote: who can do what below for a side-by-side comparison of every path.
The repository includes a Dockerfile and Compose file. The published image is available for convenience, while the source remains the canonical way to run and modify this experimental fork:
git clone https://github.com/zaherg/wp-blockmarkup-mcp-server.git
cd wp-blockmarkup-mcp-server
# Required for HTTP mode. Use a random value with at least 32 UTF-8 bytes.
printf 'MCP_AUTH_TOKEN=%s\n' "$(openssl rand -hex 32)" > .env
docker compose pull
docker compose up -d
docker compose logs -f wp-blockmarkup-mcp-serverThe default Compose binding is 127.0.0.1:3000; the MCP endpoint is http://127.0.0.1:3000/mcp. The SQLite database is stored in the named wp-data volume and survives container recreation.
Index a GitHub source inside the running container:
docker compose exec wp-blockmarkup-mcp-server node src/cli.js source:add \
--name woocommerce-blocks \
--type github-public \
--repo https://github.com/woocommerce/woocommerce \
--subfolder plugins/woocommerce-blocks \
--branch trunk \
--no-index
docker compose exec wp-blockmarkup-mcp-server node src/cli.js index --source woocommerce-blocks
docker compose exec wp-blockmarkup-mcp-server node src/cli.js statsFor private repositories, add WP_BLOCKMARKUP_GITHUB_TOKEN to .env and use a fine-grained, read-only token limited to the required repositories. Never put credentials in the repository URL.
To build instead of pulling, uncomment build: . in docker-compose.yml and comment out its image: line, then run:
docker compose build
docker compose up -dThe Dockerfile uses Node 24 slim and supports the common linux/amd64 and linux/arm64 platforms. See DEPLOYMENT.md for ingress, TLS, volume, environment-variable, and multi-architecture guidance.
Configure the client as a streamable HTTP MCP server and send the same bearer token (the value of MCP_AUTH_TOKEN in .env):
{
"mcpServers": {
"wp-blockmarkup": {
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"Authorization": "Bearer replace-with-the-value-from-.env"
}
}
}
}Hosted HTTP intentionally exposes read-only MCP tools by default. Set MCP_ENABLE_HTTP_ADMIN=true together with a valid MCP_AUTH_TOKEN to expose the four admin tools over HTTP. Admin remains off by default, and enabling it without a token fails startup even on loopback. If the service is bound beyond loopback, put it behind HTTPS and a trusted ingress, keep the backend hop private, and configure a strong token and allowed origins.
Every tool the server exposes is listed below. Dynamic blocks are represented with valid self-closing comment markup and report attributes_only because their final HTML is rendered by PHP on the WordPress site.
| MCP tool | Local CLI equivalent | Local stdio MCP | Remote HTTP MCP |
|---|---|---|---|
search_blocks |
search (equivalent search path) |
yes | yes |
get_block_schema |
schema (includes schema, attributes, variations, and examples) |
yes | yes |
get_block_markup |
partial via schema examples; no dedicated CLI command |
yes | yes |
validate_markup |
partial via validate (structural validation only) |
yes | yes |
list_block_attributes |
partial via schema; no dedicated CLI command |
yes | yes |
search_variations |
no direct CLI equivalent | yes | yes |
source_list |
source:list |
yes | yes, only with MCP_ENABLE_HTTP_ADMIN=true |
source_add |
source:add, all source types |
yes, all source types | yes, only with MCP_ENABLE_HTTP_ADMIN=true; GitHub types only |
source_remove |
source:remove |
yes | yes, only with MCP_ENABLE_HTTP_ADMIN=true |
source_index |
index |
yes | yes, only with MCP_ENABLE_HTTP_ADMIN=true; guarded by the shared-principal budget and MCP-process lock |
get_block_markup returns both the readable Markdown content response and a machine-readable
structuredContent object. The structured result contains block_name, count, and an ordered
examples array. Each example exposes title, nullable description, markup,
validation_status, and features_used (an array of strings). Stdio and Streamable HTTP share
this same tool contract.
Remote admin entries are gated by MCP_ENABLE_HTTP_ADMIN at server startup. Per-client authorization is not supported in this release; see DEPLOYMENT.md for the two-deployment pattern.
Local CLI (
node src/cli.js) runs on the same machine as the SQLite index and the GitHub token, talks to the database directly, and is the only path that can run an immediaterebuild-indexor operate without a running MCP server. It can also managelocal-foldersources, as can trusted stdio MCP. Use the CLI for setup, recovery, and one-off maintenance you might otherwise perform withsqlite3against the database.Local stdio MCP is started by an MCP client on a trusted machine. It exposes all ten tools, including the four admin ones, and there is no opt-in flag; the assumption is that whoever can spawn the process already has the same privileges as the user account running it. Use it when a single developer wants an assistant to read blocks and manage sources without leaving their workstation.
Remote HTTP MCP is the multi-client path. By default it is read-only, exactly as before. When
MCP_ENABLE_HTTP_ADMIN=trueis set, startup requiresMCP_AUTH_TOKENeven on loopback and the server exposes the four admin tools to any holder of that token. Because the opt-in is server-wide, every authenticated client is the same principal. Treat the bearer as a database root password: use HTTPS termination, a private ingress hop, ingress-side flood controls, a strong allowed-origins list, and preferably two deployments (admin-on for operators, admin-off for downstream assistants).Two things the remote path will not do, even with the opt-in on:
- Add a
local-foldersource.local-folderrequires a path on the server's filesystem and is not safe to expose to a network caller, so it is rejected at the HTTP boundary. Use the CLI or stdio.- Run two
source_index/source_removeMCP operations in parallel inside one server process. The in-process lock covers HTTP and stdio MCP calls; a second call gets a cleanisError: trueresult with the active operation and acquisition time. It does not coordinate separate CLI processes or server replicas, so operators must not run those concurrently against the same data directory.
The indexer discovers block.json files, parses metadata and JavaScript ASTs, classifies blocks as static, dynamic, or hybrid, generates feature-specific examples, and stores the results in SQLite with FTS5 search.
Generated examples are checked in tiers:
- Structural validation uses the official WordPress block serialization parser, checks delimiter syntax, JSON attributes, known attributes, types, enums, and nested blocks.
- Save-function validation checks static/hybrid output against extracted wrapper, class, style, and
InnerBlockspatterns.
Validation statuses include verified, structural_only, attributes_only, and invalid. Indexing reports success, degraded, or failure so partial source failures are not presented as complete success.
By default, local data is stored under ~/.wp-blockmarkup-mcp-server/:
~/.wp-blockmarkup-mcp-server/
blocks.db # SQLite database and FTS index
cache/ # cached Git repositories
Important runtime controls include:
MCP_TRANSPORT(stdioby default,httpfor hosted mode)MCP_HOSTandMCP_PORTMCP_AUTH_TOKEN(required for non-loopback HTTP and at least 32 UTF-8 bytes; also required on loopback whenMCP_ENABLE_HTTP_ADMIN=true)MCP_ENABLE_HTTP_ADMIN(opt-in admin tools over HTTP; defaults to off)MCP_ALLOWED_ORIGINSMCP_BODY_LIMIT_BYTESMCP_RATE_LIMIT_REQUESTSandMCP_RATE_LIMIT_WINDOW_MSMCP_ADMIN_RATE_LIMIT_REQUESTSandMCP_ADMIN_RATE_LIMIT_WINDOW_MS(shared-principal budget forsource_*calls when admin is enabled)WP_BLOCKMARKUP_GITHUB_TOKENfor private GitHub sources
Git operations run with bounded timeouts and isolated credential/config handling. Repository URLs are restricted to credential-free HTTPS github.com URLs, refs are normalized, and indexed subfolders must remain inside the checked-out source.
Use Worktrunk (wt) to manage isolated development worktrees. Create a dedicated worktree for each feature or fix instead of working directly on main. The repository hooks are defined in .config/wt.toml; run wt --help for the commands available in your installation.
npm ci
npm test
npm run lint:checknpm run lint:check runs Biome over src and tests (see biome.json for the rule set). The formatter and linter are both active. npm run lint:fix applies safe auto-fixes; npm run format:fix reformats the source.
The project currently targets Node.js 22, 24, and 26 in CI. This fork is still experimental; passing tests do not guarantee compatibility with every WordPress release, plugin, theme, MCP client, or production ingress.
This server describes block schemas and valid markup patterns. It does not know the active site's color palette, typography scale, spacing tokens, or other theme.json values. Pair it with a WordPress/site MCP that can read the active theme, then validate the final markup here before publishing it.
MIT. See LICENSE for the full text.
This project is a fork/refactor of pluginslab/wp-blockmarkup-mcp, originally authored by Marcel Schmitz and released under the MIT License. The original copyright notice is preserved in the LICENSE file as required by that license. See NOTICE for the fork attribution statement.
The fork uses the npm name @zaherg/wp-blockmarkup-mcp-server to distinguish it from the upstream wp-blockmarkup-mcp; this fork is not intended to be installed from or published to npm — run it from a clone.