CONTRIBUTING had drifted from the codebase and would have sent a new contributor down several dead ends: - required PHP 7.4 and WP 6.8, actually 8.1+ and 6.9+ - listed the MCP Adapter as a plugin to install, it has been bundled since 1.7.4 - treated Elementor as required, it has been optional since 3.0.0 - registered abilities with wp_register_ability(), 'category' => 'elementor-mcp', and a 'callback' key; the real API is emcp_tools_register_ability() with 'category' => 'emcp-tools' and 'execute_callback' - pointed at a tests/ layout and an architecture tree that no longer match - told contributors to hand-update tool counts, which are now generated - never mentioned the two-dispatcher integration pattern, which is how most new tools now get added Rewritten around what someone can actually contribute (report, docs, prompt, tool, integration), with the current setup, the real registration API, and the rules that are easy to get wrong: 'category' is required or the ability is silently dropped, never write _elementor_data directly, never guess a field name because a wrong one looks like it worked. Adds five issue forms (bug, feature, integration, idea, plus config) so reports arrive with the MCP client, the tool called, and the versions already filled in. Blank issues are disabled and the config routes docs, connection problems, Discord, security reports, and Pro support to the right place. Every link verified live. Dropped support.emcptools.com from the routing, the domain does not resolve yet. Co-Authored-By: Claude Opus 4.8 <[email protected]>
10 KiB
Contributing to EMCP Tools
Thanks for being here. Bug reports, docs fixes, prompts, and new tools are all genuinely useful, and you don't need to write PHP to help.
- Found a bug? Open a bug report
- Want a tool that doesn't exist? Request a feature
- Want a plugin supported? Request an integration
- Just an idea or a question? Start a discussion
User documentation lives at emcptools.com/docs. This file is about working on the plugin itself.
Ways to contribute
| What it involves | |
|---|---|
| Report a bug | The most valuable thing you can do. Include your MCP client, the tool you called, and what came back. |
| Improve the docs | The docs live in the website repo, but if something in this repo is wrong or stale, a PR fixing it is very welcome. |
| Contribute a prompt | A landing-page blueprint in prompts/. No PHP needed, see Contributing prompts. |
| Add a tool | A new MCP ability in an existing domain. See Adding a tool. |
| Add an integration | Support for a plugin we don't cover yet. This is the highest-leverage code contribution, see Adding an integration. |
Development setup
Requirements: WordPress 6.9+, PHP 8.1+, Composer, and WP-CLI (strongly recommended). Elementor is optional: most domains work without it, install it if you're touching the page-building tools. The MCP Adapter and the Abilities API are bundled, so there's nothing extra to install.
cd /path/to/wordpress/wp-content/plugins
git clone https://github.com/msrbuilds/elementor-mcp.git emcp-tools
cd emcp-tools
composer install
Activate the plugin, then confirm the server registered:
wp mcp-adapter list --path=/path/to/wordpress
To poke at tools interactively:
npx @modelcontextprotocol/inspector wp mcp-adapter serve \
--server=emcp-tools-server --user=admin --path=/path/to/wordpress
Note on the
pro/directory. The Pro tier lives in a separate private repository, mounted as a git submodule atpro/. A normal clone simply won't have it, and the plugin runs fine without it, every Pro unit is guarded. If you see references topro/in the code, that's why.
Repository layout
emcp-tools/
├── emcp-tools.php # Bootstrap: header, constants, requires, init
├── includes/
│ ├── class-bootstrap.php # Loads every class, wires hooks
│ ├── class-plugin.php # Singleton orchestrator
│ ├── class-elementor-data.php # Elementor document read/write layer
│ ├── class-element-factory.php # Builds valid Elementor JSON
│ ├── abilities/ # MCP tools, grouped by domain
│ │ ├── class-ability-registrar.php # Registers every group
│ │ ├── class-query-abilities.php # Discovery (Elementor)
│ │ ├── class-content-abilities.php # WordPress content
│ │ ├── forms/ # Form-plugin integrations
│ │ ├── seo/ # SEO-plugin integrations
│ │ └── addons/ # Elementor addon packs (Pro)
│ ├── modules/ # Toggleable features (Modules tab)
│ ├── themer/ # EMCP Themer (theme builder)
│ ├── security/ · performance/ # Scanners
│ ├── schemas/ · validators/ # Control-to-JSON-Schema, input validation
│ └── admin/ # Admin screens
├── prompts/ # Landing-page blueprints
└── tests/ # Public test suite
Key ideas:
- Abilities are the tools. Each one is registered with the WordPress Abilities API and surfaced over MCP.
- Never write
_elementor_datadirectly. Go through the data layer, it triggers CSS regeneration and cache busting. Raw meta writes cause bugs that only appear on the front end. - Schemas are generated, not hand-written, from Elementor's own widget controls.
Adding a tool
Add it to the ability class for its domain, or create a new class if it genuinely doesn't fit.
1. Register it. Use the emcp_tools_register_ability() wrapper, not wp_register_ability() directly:
emcp_tools_register_ability(
'emcp-tools/my-new-tool',
array(
'label' => __( 'My New Tool', 'emcp-tools' ),
'description' => __( 'What it does, written for an AI agent deciding whether to call it.', 'emcp-tools' ),
'category' => 'emcp-tools',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array(
'type' => 'integer',
'description' => __( 'The page/post ID.', 'emcp-tools' ),
),
),
'required' => array( 'post_id' ),
),
'permission_callback' => array( $this, 'check_edit_permission' ),
'execute_callback' => array( $this, 'execute_my_new_tool' ),
)
);
categoryis required. Leave it out andwp_register_ability()drops the ability silently, with no error. The tool simply never appears. This has bitten us more than once.
2. Implement it. Return an array on success, a WP_Error on failure. Never return a bare false.
3. Register the group in class-ability-registrar.php, and add the tool to get_all_tools() in includes/admin/class-admin.php so it appears on the Tools tab.
4. If it writes, deletes, or affects the whole site, ship it disabled by default: bump DEFAULTS_VERSION in the admin class and seed your slug. Destructive tools should also require confirm: true.
Descriptions are a UX surface. An agent picks tools by reading them, so write for that reader: say what it does and when to use it, not just what it's called.
Adding an integration
Support for a third-party plugin is the most useful code contribution, and it follows a fixed shape: two dispatcher tools, <plugin>-read and <plugin>-write, each taking { operation, arguments }. That keeps the tool list small no matter how many plugins we support.
Look at includes/abilities/forms/ or includes/abilities/seo/ for working examples. Both have an abstract base class doing the dispatching, so a new adapter is mostly a map of operations.
Rules that matter:
- Register only when the plugin is active. Never assume.
- Use the plugin's public API, not its database tables. Tables change without notice; APIs are contracts.
- Reads on by default, writes off. Anything destructive needs
confirm: true. - Never guess a field or control name. Read it from the plugin. Many systems (Elementor and Spectra especially) accept any key you send without complaining, so a wrong name looks like it worked and silently does nothing.
Please open an integration request before starting a large one, so we can agree the operation list first.
Contributing prompts
A prompt is a complete landing-page blueprint in prompts/. Include a design system (colours, typography, spacing), the full page structure, image search keywords, and the execution order. Use one of the existing files as your template, and name it INDUSTRY_NAME.md in caps with underscores.
Test it end to end against a real site before submitting: paste it into your AI client and confirm the page actually builds.
Coding standards
WordPress coding standards, strictly.
- Naming:
snake_casefunctions and variables,Upper_Snake_Caseclasses,UPPER_SNAKEconstants. - Prefixes:
EMCP_Tools_for classes,emcp_tools_for functions, hooks, and options. - Text domain:
emcp-tools. Every user-facing string goes through__()/esc_html__(). - Security is not optional. Sanitize input, escape output,
$wpdb->prepare()for SQL, verify nonces, check capabilities before anything privileged. - PHP 8.1+. Typed properties, union types, and named arguments are all fine.
Testing
The public suite needs no private submodule:
composer install
vendor/bin/phpunit -c tests/phpunit.xml
Put new tests in tests/, named SomethingTest.php. Test the pure logic, validators, schema mapping, dispatcher routing, permission delegation, rather than trying to boot WordPress.
Test the trap, not just the happy path. The most valuable tests we have pin down behaviour that silently breaks: a permission that must not escalate, a signature that must not appear verbatim on disk, a confirm gate that must refuse. If you fixed a bug, add the test that would have caught it.
Submitting a pull request
- Fork, then branch from
main(git checkout -b feature/my-tool). - Make the change, and run
php -lon every file you touched. - Run the public test suite.
- Add a
CHANGELOG.mdentry describing the change from a user's point of view. - Open the PR against
main.
Guidelines:
- One feature or fix per PR. Small PRs get reviewed quickly; large mixed ones stall.
- Say what problem it solves, not just what it changes.
- If you found something surprising while building it, put that in the PR. It's often the most useful part.
- Don't update tool counts by hand, they're generated.
Reporting bugs
Use the bug report template. The details that actually speed up a fix:
- Plugin, WordPress, and PHP versions (and Elementor, if relevant)
- Which MCP client you're using
- The tool you called and the arguments
- What you expected versus what happened
- Anything from
wp-content/debug.log
For anything security-sensitive, please don't open a public issue. Email [email protected] instead.
Every contribution helps, including the ones that just tell us something is broken. Thank you.