drupal-mcp-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drupal_mcp_server-0.1.0/.env.example +38 -0
- drupal_mcp_server-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
- drupal_mcp_server-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- drupal_mcp_server-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- drupal_mcp_server-0.1.0/.github/workflows/ci.yml +75 -0
- drupal_mcp_server-0.1.0/.github/workflows/docs.yml +40 -0
- drupal_mcp_server-0.1.0/.github/workflows/release.yml +38 -0
- drupal_mcp_server-0.1.0/.gitignore +78 -0
- drupal_mcp_server-0.1.0/CHANGELOG.md +306 -0
- drupal_mcp_server-0.1.0/CODE_OF_CONDUCT.md +61 -0
- drupal_mcp_server-0.1.0/CONTRIBUTING.md +202 -0
- drupal_mcp_server-0.1.0/LICENSE +21 -0
- drupal_mcp_server-0.1.0/MAINTAINERS.md +40 -0
- drupal_mcp_server-0.1.0/PKG-INFO +503 -0
- drupal_mcp_server-0.1.0/README.md +461 -0
- drupal_mcp_server-0.1.0/SECURITY.md +41 -0
- drupal_mcp_server-0.1.0/docs/adr/0001-use-fastmcp.md +39 -0
- drupal_mcp_server-0.1.0/docs/adr/0002-pydantic-settings.md +38 -0
- drupal_mcp_server-0.1.0/docs/adr/0003-subprocess-drush.md +38 -0
- drupal_mcp_server-0.1.0/docs/adr/0004-http-transport.md +37 -0
- drupal_mcp_server-0.1.0/docs/architecture.md +213 -0
- drupal_mcp_server-0.1.0/docs/configuration.md +138 -0
- drupal_mcp_server-0.1.0/docs/docstring_standard.md +86 -0
- drupal_mcp_server-0.1.0/docs/index.md +39 -0
- drupal_mcp_server-0.1.0/docs/installation.md +42 -0
- drupal_mcp_server-0.1.0/docs/migration.md +139 -0
- drupal_mcp_server-0.1.0/docs/tools/content.md +34 -0
- drupal_mcp_server-0.1.0/docs/tools/devops.md +60 -0
- drupal_mcp_server-0.1.0/docs/tools/index.md +88 -0
- drupal_mcp_server-0.1.0/docs/tools/users.md +34 -0
- drupal_mcp_server-0.1.0/examples/.env.example +38 -0
- drupal_mcp_server-0.1.0/examples/README.md +132 -0
- drupal_mcp_server-0.1.0/examples/basic_usage.py +69 -0
- drupal_mcp_server-0.1.0/examples/content_workflow.py +97 -0
- drupal_mcp_server-0.1.0/examples/custom_tool.py +129 -0
- drupal_mcp_server-0.1.0/examples/migration_workflow.py +106 -0
- drupal_mcp_server-0.1.0/mkdocs.yml +60 -0
- drupal_mcp_server-0.1.0/pyproject.toml +80 -0
- drupal_mcp_server-0.1.0/scripts/lint.sh +14 -0
- drupal_mcp_server-0.1.0/scripts/run.sh +15 -0
- drupal_mcp_server-0.1.0/scripts/setup.sh +40 -0
- drupal_mcp_server-0.1.0/scripts/test.sh +10 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/__init__.py +3 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/auth/__init__.py +94 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/auth/basic.py +10 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/auth/mcp_oauth.py +256 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/auth/session.py +108 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/auth/token.py +9 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/clients/__init__.py +7 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/clients/base.py +163 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/clients/drush.py +150 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/clients/graphql.py +182 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/clients/jsonapi.py +248 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/config.py +84 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/errors.py +45 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/exceptions.py +77 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/logging_config.py +37 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/schemas.py +55 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/server.py +233 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/services/__init__.py +6 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/services/ai_service.py +89 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/services/migration_service.py +20 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/services/site_manager.py +43 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/__init__.py +1 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/_utils.py +239 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/accessibility.py +264 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/ai.py +57 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/analysis.py +460 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/code_audit.py +227 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/comments.py +224 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/commerce.py +443 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/config.py +248 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/contact.py +189 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/content.py +561 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/database.py +79 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/devops.py +501 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/drupal_org.py +525 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/feeds.py +224 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/fields.py +227 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/headless.py +231 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/layout_builder.py +569 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/media_enhanced.py +193 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/menus.py +233 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/metatag.py +229 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/migration.py +326 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/moderation.py +547 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/module_dev.py +1510 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/multilingual.py +620 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/paragraphs.py +235 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/pathauto.py +186 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/performance.py +153 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/redirects.py +197 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/revisions.py +196 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/scheduler.py +309 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/search_api.py +280 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/security_audit.py +265 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/taxonomy.py +170 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/testing.py +249 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/theme_dev.py +755 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/users.py +209 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/views.py +336 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/tools/webform.py +564 -0
- drupal_mcp_server-0.1.0/src/drupal_mcp_server/validation.py +173 -0
- drupal_mcp_server-0.1.0/tests/__init__.py +0 -0
- drupal_mcp_server-0.1.0/tests/integration/test_server.py +247 -0
- drupal_mcp_server-0.1.0/tests/live/README.md +301 -0
- drupal_mcp_server-0.1.0/tests/live/bootstrap.py +258 -0
- drupal_mcp_server-0.1.0/tests/live/filter_check.py +186 -0
- drupal_mcp_server-0.1.0/tests/live/install_modules.py +258 -0
- drupal_mcp_server-0.1.0/tests/live/layout_builder_test.py +198 -0
- drupal_mcp_server-0.1.0/tests/live/mcp_call.py +72 -0
- drupal_mcp_server-0.1.0/tests/live/queue_test.py +276 -0
- drupal_mcp_server-0.1.0/tests/live/resource_check.py +150 -0
- drupal_mcp_server-0.1.0/tests/live/seed.php +96 -0
- drupal_mcp_server-0.1.0/tests/live/smoke_test.py +106 -0
- drupal_mcp_server-0.1.0/tests/live/sweep.py +764 -0
- drupal_mcp_server-0.1.0/tests/unit/__init__.py +0 -0
- drupal_mcp_server-0.1.0/tests/unit/test_auth.py +336 -0
- drupal_mcp_server-0.1.0/tests/unit/test_clients.py +731 -0
- drupal_mcp_server-0.1.0/tests/unit/test_content_tools.py +154 -0
- drupal_mcp_server-0.1.0/tests/unit/test_drupal_org.py +418 -0
- drupal_mcp_server-0.1.0/tests/unit/test_errors.py +99 -0
- drupal_mcp_server-0.1.0/tests/unit/test_handler_and_translation_tools.py +386 -0
- drupal_mcp_server-0.1.0/tests/unit/test_logging_config.py +41 -0
- drupal_mcp_server-0.1.0/tests/unit/test_moderation_transitions.py +467 -0
- drupal_mcp_server-0.1.0/tests/unit/test_resource_names.py +121 -0
- drupal_mcp_server-0.1.0/tests/unit/test_revisions.py +248 -0
- drupal_mcp_server-0.1.0/tests/unit/test_scheduler_tools.py +229 -0
- drupal_mcp_server-0.1.0/tests/unit/test_schemas.py +94 -0
- drupal_mcp_server-0.1.0/tests/unit/test_sweep_harness.py +440 -0
- drupal_mcp_server-0.1.0/tests/unit/test_tool_utils.py +950 -0
- drupal_mcp_server-0.1.0/tests/unit/test_tools.py +6 -0
- drupal_mcp_server-0.1.0/tests/unit/test_validation.py +237 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Drupal MCP Server Configuration
|
|
2
|
+
# Copy to .env and fill in your values
|
|
3
|
+
|
|
4
|
+
# === Drupal Connection ===
|
|
5
|
+
DRUPAL_BASE_URL=https://your-drupal-site.com
|
|
6
|
+
DRUPAL_AUTH_TYPE=token
|
|
7
|
+
DRUPAL_AUTH_TOKEN=your-api-token-here
|
|
8
|
+
|
|
9
|
+
# For basic auth:
|
|
10
|
+
# DRUPAL_AUTH_TYPE=basic
|
|
11
|
+
# DRUPAL_USERNAME=admin
|
|
12
|
+
# DRUPAL_PASSWORD=your-password
|
|
13
|
+
|
|
14
|
+
# For OAuth2:
|
|
15
|
+
# DRUPAL_AUTH_TYPE=oauth2
|
|
16
|
+
# DRUPAL_OAUTH_CLIENT_ID=your-client-id
|
|
17
|
+
# DRUPAL_OAUTH_CLIENT_SECRET=your-client-secret
|
|
18
|
+
# DRUPAL_OAUTH_TOKEN_URL=https://your-site.com/oauth/token
|
|
19
|
+
|
|
20
|
+
# === AI Provider (choose one) ===
|
|
21
|
+
AI_PROVIDER=ollama
|
|
22
|
+
OLLAMA_BASE_URL=http://localhost:11434
|
|
23
|
+
|
|
24
|
+
# AI_PROVIDER=openai
|
|
25
|
+
# OPENAI_API_KEY=sk-...
|
|
26
|
+
|
|
27
|
+
# AI_PROVIDER=anthropic
|
|
28
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
29
|
+
|
|
30
|
+
# AI_PROVIDER=google
|
|
31
|
+
# GOOGLE_API_KEY=...
|
|
32
|
+
|
|
33
|
+
# === Drush Configuration ===
|
|
34
|
+
DRUSH_PATH=/usr/local/bin/drush
|
|
35
|
+
DRUSH_ROOT=/var/www/html
|
|
36
|
+
|
|
37
|
+
# === DDEV Configuration ===
|
|
38
|
+
DDEV_PROJECT_PATH=/path/to/your/drupal-project
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: '[BUG] '
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Describe the bug
|
|
10
|
+
A clear and concise description of what the bug is.
|
|
11
|
+
|
|
12
|
+
## To Reproduce
|
|
13
|
+
Steps to reproduce the behavior:
|
|
14
|
+
1. Configure ... with '...'
|
|
15
|
+
2. Run tool '...'
|
|
16
|
+
3. See error
|
|
17
|
+
|
|
18
|
+
## Expected behavior
|
|
19
|
+
A clear and concise description of what you expected to happen.
|
|
20
|
+
|
|
21
|
+
## Environment
|
|
22
|
+
- OS: [e.g., macOS 15, Ubuntu 22.04]
|
|
23
|
+
- Python version: [e.g., 3.11.0]
|
|
24
|
+
- Drupal version: [e.g., 10.2]
|
|
25
|
+
- MCP transport: [stdio / http / sse / streamable-http]
|
|
26
|
+
- Installation method: [pip install / source]
|
|
27
|
+
|
|
28
|
+
## Logs
|
|
29
|
+
```
|
|
30
|
+
Paste relevant error logs or tool output here
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Additional context
|
|
34
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: '[FEAT] '
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Is your feature request related to a problem?
|
|
10
|
+
A clear and concise description of what the problem is.
|
|
11
|
+
|
|
12
|
+
## Describe the solution you'd like
|
|
13
|
+
A clear and concise description of what you want to happen.
|
|
14
|
+
|
|
15
|
+
## Describe alternatives you've considered
|
|
16
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
17
|
+
|
|
18
|
+
## Which tool category does this belong to?
|
|
19
|
+
- [ ] Content management
|
|
20
|
+
- [ ] Users & permissions
|
|
21
|
+
- [ ] Taxonomy
|
|
22
|
+
- [ ] Media
|
|
23
|
+
- [ ] DevOps / Drush
|
|
24
|
+
- [ ] Migration
|
|
25
|
+
- [ ] AI-powered tools
|
|
26
|
+
- [ ] Security audit
|
|
27
|
+
- [ ] Code quality
|
|
28
|
+
- [ ] Other: ___________
|
|
29
|
+
|
|
30
|
+
## Additional context
|
|
31
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
Please include a summary of the change and which issue is fixed.
|
|
4
|
+
|
|
5
|
+
Fixes # (issue number)
|
|
6
|
+
|
|
7
|
+
## Type of change
|
|
8
|
+
|
|
9
|
+
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
10
|
+
- [ ] New feature (non-breaking change which adds functionality)
|
|
11
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
12
|
+
- [ ] Documentation update
|
|
13
|
+
- [ ] Security fix
|
|
14
|
+
- [ ] Performance improvement
|
|
15
|
+
|
|
16
|
+
## How has this been tested?
|
|
17
|
+
|
|
18
|
+
Please describe the tests that you ran to verify your changes.
|
|
19
|
+
|
|
20
|
+
- [ ] Unit tests pass (`pytest`)
|
|
21
|
+
- [ ] Linting passes (`ruff check src/ tests/`)
|
|
22
|
+
- [ ] Type checking passes (`mypy src/drupal_mcp_server/`)
|
|
23
|
+
- [ ] Integration tests pass (describe setup)
|
|
24
|
+
|
|
25
|
+
## Checklist
|
|
26
|
+
|
|
27
|
+
- [ ] My code follows the project style guidelines
|
|
28
|
+
- [ ] I have added tests that prove my fix is effective or that my feature works
|
|
29
|
+
- [ ] New and existing unit tests pass locally with my changes
|
|
30
|
+
- [ ] I have updated the documentation accordingly
|
|
31
|
+
- [ ] I have added input validation for any new user-facing parameters
|
|
32
|
+
- [ ] Security considerations have been addressed
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Lint with ruff
|
|
30
|
+
run: ruff check src/ tests/
|
|
31
|
+
|
|
32
|
+
- name: Type check with mypy
|
|
33
|
+
run: mypy src/drupal_mcp_server/
|
|
34
|
+
|
|
35
|
+
- name: Run tests with coverage
|
|
36
|
+
run: |
|
|
37
|
+
# Measure everything and report it, but gate only the code that can be
|
|
38
|
+
# tested without a live Drupal. The large majority of the statements
|
|
39
|
+
# are in the tool modules, which only tests/live/sweep.py can
|
|
40
|
+
# exercise, so a single total threshold is unreachable by construction:
|
|
41
|
+
# even 100% coverage of everything else would total just 25%.
|
|
42
|
+
pytest --cov=src/drupal_mcp_server --cov-report=term-missing --cov-report=xml -v
|
|
43
|
+
coverage report --omit="*/drupal_mcp_server/tools/*" --fail-under=70
|
|
44
|
+
|
|
45
|
+
- name: Upload coverage to Codecov
|
|
46
|
+
uses: codecov/codecov-action@v4
|
|
47
|
+
with:
|
|
48
|
+
files: ./coverage.xml
|
|
49
|
+
fail_ci_if_error: true
|
|
50
|
+
|
|
51
|
+
- name: Dependency vulnerability scan
|
|
52
|
+
run: pip-audit
|
|
53
|
+
|
|
54
|
+
build:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
needs: test
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
|
|
61
|
+
- name: Set up Python
|
|
62
|
+
uses: actions/setup-python@v5
|
|
63
|
+
with:
|
|
64
|
+
python-version: "3.11"
|
|
65
|
+
|
|
66
|
+
- name: Build package
|
|
67
|
+
run: |
|
|
68
|
+
pip install build
|
|
69
|
+
python -m build
|
|
70
|
+
|
|
71
|
+
- name: Upload artifacts
|
|
72
|
+
uses: actions/upload-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: dist
|
|
75
|
+
path: dist/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'docs/**'
|
|
8
|
+
- 'mkdocs.yml'
|
|
9
|
+
- 'README.md'
|
|
10
|
+
pull_request:
|
|
11
|
+
paths:
|
|
12
|
+
- 'docs/**'
|
|
13
|
+
- 'mkdocs.yml'
|
|
14
|
+
- 'README.md'
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
docs:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.11"
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: |
|
|
30
|
+
pip install mkdocs-material mkdocstrings[python]
|
|
31
|
+
|
|
32
|
+
- name: Build documentation
|
|
33
|
+
run: mkdocs build --strict
|
|
34
|
+
|
|
35
|
+
- name: Deploy to GitHub Pages
|
|
36
|
+
if: github.ref == 'refs/heads/main'
|
|
37
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
38
|
+
with:
|
|
39
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
publish_dir: ./site
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: |
|
|
26
|
+
pip install build
|
|
27
|
+
|
|
28
|
+
- name: Build package
|
|
29
|
+
run: python -m build
|
|
30
|
+
|
|
31
|
+
- name: Create GitHub Release
|
|
32
|
+
uses: softprops/action-gh-release@v2
|
|
33
|
+
with:
|
|
34
|
+
files: dist/*
|
|
35
|
+
generate_release_notes: true
|
|
36
|
+
|
|
37
|
+
- name: Publish to PyPI
|
|
38
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
venv/
|
|
25
|
+
ENV/
|
|
26
|
+
env/
|
|
27
|
+
.venv/
|
|
28
|
+
|
|
29
|
+
# Local DDEV test site (see tests/live/README.md to rebuild it)
|
|
30
|
+
drupal-live/
|
|
31
|
+
|
|
32
|
+
# Coverage output
|
|
33
|
+
coverage.xml
|
|
34
|
+
.coverage
|
|
35
|
+
|
|
36
|
+
# Live-test output
|
|
37
|
+
tests/live/sweep-results.json
|
|
38
|
+
|
|
39
|
+
# IDE
|
|
40
|
+
.idea/
|
|
41
|
+
.vscode/
|
|
42
|
+
*.swp
|
|
43
|
+
*.swo
|
|
44
|
+
*~
|
|
45
|
+
|
|
46
|
+
# Testing
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
.coverage
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
|
|
53
|
+
# MyPy
|
|
54
|
+
.mypy_cache/
|
|
55
|
+
|
|
56
|
+
# Ruff
|
|
57
|
+
.ruff_cache/
|
|
58
|
+
|
|
59
|
+
# Environment
|
|
60
|
+
.env
|
|
61
|
+
.env.local
|
|
62
|
+
.env.*.local
|
|
63
|
+
|
|
64
|
+
# OS
|
|
65
|
+
.DS_Store
|
|
66
|
+
Thumbs.db
|
|
67
|
+
|
|
68
|
+
# Project specific
|
|
69
|
+
*.log
|
|
70
|
+
*.sql
|
|
71
|
+
*.tar.gz
|
|
72
|
+
config/export/
|
|
73
|
+
config/sync/
|
|
74
|
+
|
|
75
|
+
#agents
|
|
76
|
+
.omc
|
|
77
|
+
.pi
|
|
78
|
+
.mutagen-*
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- Fix command injection vulnerabilities in code audit and security audit tools
|
|
13
|
+
- Fix SQL injection in database query tools
|
|
14
|
+
- Fix path traversal in media upload tool
|
|
15
|
+
- Add input validation for URLs, queue names, and file paths
|
|
16
|
+
- Add authentication check to /mcp/tools/list endpoint
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Add `moderation_transition_delete`, giving `moderation_transition_create` the
|
|
21
|
+
counterpart it never had. The tool count is now 367
|
|
22
|
+
- Add Lando support to SiteManager
|
|
23
|
+
- Add subprocess timeout to DrushClient
|
|
24
|
+
- Add OAuth token fetching to AuthManager
|
|
25
|
+
- Create shared tools/_utils.py with ensure_dict, validate_pagination, build_pagination_params
|
|
26
|
+
- Add pagination validation to list tools
|
|
27
|
+
- Add input validation to accessibility and performance tools
|
|
28
|
+
- Add LICENSE, CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md
|
|
29
|
+
- Add GitHub issue and PR templates
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Correct what the sweep asks for, not just how it reads the answer. Every
|
|
34
|
+
parameter ending in `id` had been answered with the fixture node's id, so
|
|
35
|
+
`view_id`, `menu_id`, `webform_id` and eighty-odd others were all sent one
|
|
36
|
+
node number and answered a lookup miss: **84 of 174 refusals** in the run
|
|
37
|
+
before this were the harness asking the wrong question, not tools refusing,
|
|
38
|
+
and a miss says nothing about whether a tool works. Of 367 tools, 192 declare
|
|
39
|
+
no id-shaped parameter, 93 read-only ones now get a real id for their own
|
|
40
|
+
resource — with the tool's own name deciding where one parameter covers
|
|
41
|
+
several, since `config_id` is a field config, a metatag config or a search
|
|
42
|
+
index depending on who is asking — and 82 mutating ones deliberately keep
|
|
43
|
+
junk ids, because `views_update` with a real view id would save the view back
|
|
44
|
+
with the harness's synthesised arguments and every later `views_execute`
|
|
45
|
+
would then run against a view this run broke. The remaining 8 read-only tools
|
|
46
|
+
keep junk because the site has no such entity to name. The fixture node also
|
|
47
|
+
re-checks and re-provisions itself: `delete_node` runs mid-sweep and took the
|
|
48
|
+
node with it, which alone 404'd `revision_list`, `revision_get`,
|
|
49
|
+
`revision_revert`, `revision_diff`, `translation_status`, `translation_add`,
|
|
50
|
+
`moderation_transition_execute`, `layout_builder_override_remove` and
|
|
51
|
+
`accessibility_aria_validate`
|
|
52
|
+
- Keep `feeds_log_list` an explicit refusal rather than implement it. Feeds 3.x
|
|
53
|
+
has no log table, no `feeds*` service and no log entity to read, so there is
|
|
54
|
+
nothing behind it to query; the tool names `imports_import_status`, which does
|
|
55
|
+
answer, instead of returning an empty list that would read as "no log entries"
|
|
56
|
+
- Definitive full re-sweep (OK 195 / REJECTED 156 / BLOCKED 7 /
|
|
57
|
+
UPSTREAM 4 / DEAD 0 / SKIPPED 5, 367 total); no tool that used to work
|
|
58
|
+
regressed and nothing is dead. Harness follow-up fixes (`entity_id` and
|
|
59
|
+
revision-id handling) pushed six more tools past their lookup so they reach
|
|
60
|
+
their own logic, where they report honestly.
|
|
61
|
+
|
|
62
|
+
- Re-sweep all 366 tools against the provisioned site. BLOCKED fell from 71 to
|
|
63
|
+
5, OK rose from 156 to 181, and nothing is dead. 25 tools that could not be
|
|
64
|
+
reached at all now run; 41 reach Drupal and are turned away with a reason
|
|
65
|
+
instead of stopping at a 404. The five still BLOCKED are two `feeds_feed`
|
|
66
|
+
tools needing a feed type with a `feeds_item` field, two `moderation_state`
|
|
67
|
+
tools for a resource Drupal never exposes, and `split_config` needing a
|
|
68
|
+
`./config/sync` directory. The one UPSTREAM is `revision_delete`, since
|
|
69
|
+
JSON:API has no revision collection endpoint
|
|
70
|
+
- Correct the sweep's own accounting. Six tools — `translation_add`,
|
|
71
|
+
`translation_update`, the three `interface_translation_*` tools and
|
|
72
|
+
`config_translation_add` — were reported OK on every run while never being
|
|
73
|
+
called once. When arguments fail FastMCP's validation the answer is prose
|
|
74
|
+
rather than JSON, and nothing in the classifier matched it, so the
|
|
75
|
+
fall-through said pass; the server logged `missing_argument` for each and
|
|
76
|
+
the report showed a green result. A refused call now counts as refused,
|
|
77
|
+
through the SDK's own `isError` signal rather than a guess at wording. The
|
|
78
|
+
sweep also stopped filling `langcode` with a junk value (JSON:API
|
|
79
|
+
write-protects that field on a multilingual site whatever it says, so
|
|
80
|
+
`create_term` answered 403 about a permission instead of running),
|
|
81
|
+
started giving `ddev_exec` a command the container has, gave
|
|
82
|
+
`drush_watchdog` the enum and the number drush wants, and names a queue
|
|
83
|
+
that exists
|
|
84
|
+
- Provision the live test site with the modules the tools assume are there:
|
|
85
|
+
media, comment, contact, language, config_translation, workflows,
|
|
86
|
+
content_moderation and commerce plus its submodules from core and contrib,
|
|
87
|
+
and token, pathauto, redirect, metatag, paragraphs, search_api, facets,
|
|
88
|
+
webform, feeds and graphql from drupal.org, with phpstan, drupal-check,
|
|
89
|
+
phpcodesniffer and coder for the code-quality tools. Drupal's entity types
|
|
90
|
+
went from 26 to 78 and the JSON:API index from 27 resources to 76, which
|
|
91
|
+
turned most of the sweep's BLOCKED bucket into a real verdict. The site is
|
|
92
|
+
also multilingual now -- English and French -- because a single-language site
|
|
93
|
+
cannot exercise translation behaviour, and that is what surfaced the langcode
|
|
94
|
+
bug below
|
|
95
|
+
- Add three live suites. `tests/live/install_modules.py` provisions the modules
|
|
96
|
+
the sweep needs using the server's own `composer_require` and `drush_command`
|
|
97
|
+
tools, so bootstrapping the site is also a test of them; `resource_check.py`
|
|
98
|
+
asks Drupal for the entity types it knows and flags any resource name our
|
|
99
|
+
tools ask for that cannot exist, which is how the `views_view` and
|
|
100
|
+
`moderation_state` bugs were found; `mcp_call.py` is a one-call driver for
|
|
101
|
+
operating the site through the server instead of a shell
|
|
102
|
+
|
|
103
|
+
- Gate CI coverage on the code testable offline (non-tool core ≥70%) instead of
|
|
104
|
+
the total: 86% of statements live in the tool modules, which only a live sweep
|
|
105
|
+
can exercise, so a single total threshold is unreachable by construction.
|
|
106
|
+
`coverage.xml` is now actually generated, which the Codecov step already
|
|
107
|
+
required with `fail_ci_if_error: true`
|
|
108
|
+
- Extend `layout_builder_override_remove` with an optional `entity_id`, since an
|
|
109
|
+
override belongs to an entity and the old signature could never name one
|
|
110
|
+
- Document what live testing found, and how a failure arrives, in
|
|
111
|
+
`tests/live/README.md` and the README's Development section
|
|
112
|
+
- Extract _ensure_dict from 35+ tool modules into shared utility
|
|
113
|
+
- Remove duplicate service implementations (ai.py, migration.py, site.py)
|
|
114
|
+
- Clean up unused imports across all tool modules
|
|
115
|
+
- Update ARTIFACT.md to reflect actual project state
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- Make an issued OAuth token worth issuing. HTTP transport with OAuth enabled
|
|
120
|
+
refused everyone: `load_access_token` returned the dict the token was stored
|
|
121
|
+
in where the declared type is FastMCP's `AccessToken`, so the first thing the
|
|
122
|
+
bearer middleware did with a good token -- `auth_info.expires_at` -- raised
|
|
123
|
+
`AttributeError: 'dict' object has no attribute 'expires_at'` and the request
|
|
124
|
+
was refused exactly as a forged one would be. That made the defect invisible
|
|
125
|
+
by construction, because the fail-closed path and the fail-for-everyone path
|
|
126
|
+
are the same 401: the only OAuth behaviour anyone had ever observed was the
|
|
127
|
+
reassuring one, and the 401 that read as security working was also masking an
|
|
128
|
+
unusable transport. `expires_at` was stored as a float from `time.time()`
|
|
129
|
+
while `AccessToken.expires_at` is an int, so pydantic would have refused it
|
|
130
|
+
regardless. Verified three ways -- unit tests over authorize/exchange/load,
|
|
131
|
+
an ASGI test against FastMCP's real HTTP app (401 unauthenticated, 200 for
|
|
132
|
+
the issued token, 401 for a forged one), and a replay of the old
|
|
133
|
+
implementation that reproduces the AttributeError
|
|
134
|
+
- Implement twelve tools that refused or had never been written. The five
|
|
135
|
+
`moderation_transition_*` tools all read `$wf->get("transitions")`, a key the
|
|
136
|
+
workflow entity has never had — transitions live on
|
|
137
|
+
`Workflow::getTypePlugin()->getTransitions()` — so `list` answered
|
|
138
|
+
`count: 0` against a five-transition workflow, which reads as a pass because
|
|
139
|
+
an empty list is not an error, `get` reported "No transition with id 7" for a
|
|
140
|
+
transition present by name, and `execute`, `create` and the new `delete` were
|
|
141
|
+
unreachable behind the same mistake. The four `interface_translation_*` tools
|
|
142
|
+
read `TranslationString::getLanguage()` and `getTranslation()`, methods that
|
|
143
|
+
class does not have; they now go through `locale.storage`, and a live round
|
|
144
|
+
trip verified the list (13666 French rows, with search and context filters),
|
|
145
|
+
add, update and delete, including a source string carrying a non-ASCII
|
|
146
|
+
character and a `$`. Writing a translation also dispatches
|
|
147
|
+
`LocaleEvents::SAVE_TRANSLATION` through `_locale_refresh_translations()`,
|
|
148
|
+
which invalidates the `locale` cache tag every `LocaleLookup` row depends on
|
|
149
|
+
— `invalidateByPrefix` and `cache_clear_all` do not exist in Drupal 11. The
|
|
150
|
+
three `webform_handler_*` tools answered `absent_entity` because they had
|
|
151
|
+
never been written; they now use `plugin.manager.webform.handler` with
|
|
152
|
+
`Webform::addWebformHandler` / `updateWebformHandler` / `deleteWebformHandler`,
|
|
153
|
+
take `webform_id` to disambiguate a handler id that is unique per webform
|
|
154
|
+
rather than site-wide, replace configuration keys whole so omitted ones keep
|
|
155
|
+
their current values, and locate handlers through
|
|
156
|
+
`getHandlers()->getInstanceIds()` instead of `Webform::getHandler()`, which
|
|
157
|
+
throws `PluginNotFoundException` for an unseen id rather than returning NULL.
|
|
158
|
+
A `contact` form started this with two handlers and ended with two after a
|
|
159
|
+
full create/update/delete — the point of a round trip
|
|
160
|
+
- Hand structured values to `php:eval` as base64 rather than as JSON, which is
|
|
161
|
+
not PHP: it emits `\uXXXX` and PHP has no such escape, so non-ASCII mangles
|
|
162
|
+
silently; `$` interpolates into the code; and `{"k":1}` is a parse error.
|
|
163
|
+
`_utils.php_text()` and `php_json()` carry a string as `base64_decode(...)`
|
|
164
|
+
and an array as `json_decode(base64_decode(...), TRUE)`
|
|
165
|
+
- Stop chasing the login redirect to a URL the site invented. Drupal answers a
|
|
166
|
+
good login with 303 to the account page, built from the scheme and host it
|
|
167
|
+
saw the request arrive on, so when the site reported itself as plain `http`
|
|
168
|
+
while the client was on HTTPS the redirect was fetched as HTTP against a TLS
|
|
169
|
+
port, nginx answered 400, and the session cookie that same response had just
|
|
170
|
+
issued was discarded — leaving only a startup warning. Every write then fell
|
|
171
|
+
back to basic auth, which Drupal's mutating endpoints reject, on any site
|
|
172
|
+
whose self-issued URL disagrees with the client's behind a proxy or a
|
|
173
|
+
misconfigured base_url. The POST is no longer followed, because only its
|
|
174
|
+
status and its cookie were ever wanted; bad credentials still fail, since
|
|
175
|
+
Drupal refuses them with 200 and no cookie
|
|
176
|
+
- Make the sweep follow the site's links on the origin it was given. Drupal
|
|
177
|
+
answers `http://` when reached through a port-mapped HTTPS endpoint, and the
|
|
178
|
+
harness followed those verbatim — plain HTTP to a TLS listener, which nginx
|
|
179
|
+
refuses with 400 — so a whole run died before calling a single tool.
|
|
180
|
+
`filter_check.py` hardcoded its base URL while every other live script honours
|
|
181
|
+
`DRUPAL_BASE_URL`, so it could not be pointed at all
|
|
182
|
+
- Ask whether a node exists in a way JSON:API can answer. The check built the
|
|
183
|
+
resource *name* into the path (`/jsonapi/node--article/7`, where the resource
|
|
184
|
+
lives at `/jsonapi/node/article`) and asked for an internal id where rows are
|
|
185
|
+
addressed by uuid, so it 404'd for nodes that exist and reported every fixture
|
|
186
|
+
as gone — which would have had the harness re-provisioning on every check
|
|
187
|
+
instead of only when `delete_node` removed one
|
|
188
|
+
- Make three tools answer with the reason instead of a bare error.
|
|
189
|
+
`views_execute` could never have run: it asked for a `views.views` service
|
|
190
|
+
that does not exist (the executable is built from the view's config entity by
|
|
191
|
+
`views.executable`), read the row's entity through a magic property that
|
|
192
|
+
`isset()` always reports as absent, and took no care over the display. It now
|
|
193
|
+
loads the view, checks the display against the ones the view actually has --
|
|
194
|
+
reporting the rest when the caller's is wrong -- and reads `_entity`.
|
|
195
|
+
`graphql_query` and `graphql_schema_get` returned a raw 404 whenever the site
|
|
196
|
+
had no GraphQL server, which is the state a plain install leaves it in, since
|
|
197
|
+
the module ships no default; they now say that, and name the config entity
|
|
198
|
+
and the permission that fix it. `paragraphs_item_list` returned a bare 404
|
|
199
|
+
for a bundle that does not exist, which reads as "paragraphs are missing"
|
|
200
|
+
rather than "that type is not configured here"; it now lists the types the
|
|
201
|
+
site does have
|
|
202
|
+
- Fix eleven hard-coded JSON:API filters that Drupal rejected outright, each
|
|
203
|
+
returning an empty result on every call on every site. `commerce_product`,
|
|
204
|
+
`commerce_promotion` and `commerce_shipping_method` filtered on `store` where
|
|
205
|
+
the field is the plural `stores`, and `commerce_order` on `store` where it is
|
|
206
|
+
the `store_id` reference; all four needed the `.id` specifier that entity
|
|
207
|
+
references require. `webform_submission` filtered on `webform` where the field
|
|
208
|
+
is `webform_id`, also a reference. `paragraphs_item_list` filtered on
|
|
209
|
+
`paragraphs_type`, which is not a field at all -- a paragraph's type is its
|
|
210
|
+
bundle, so it now addresses `paragraph--{type}`. `pathauto_pattern_list`
|
|
211
|
+
filtered on `entity_type` and `bundle`, which live in a pattern's id as
|
|
212
|
+
`{entity_type}--{bundle}`, so it now filters on `id` when it has both.
|
|
213
|
+
`comments_thread_list` filtered on `pid` for top-level comments, but `pid` is
|
|
214
|
+
a reference to the parent comment, so it 400'd -- and `pid.id = 0` would have
|
|
215
|
+
returned nothing, since no comment has id 0; the whole thread is returned now,
|
|
216
|
+
with each row's `pid` available for building the tree. `facets_facet_list`
|
|
217
|
+
filtered on `index`, but a facet attaches to a facet source rather than to an
|
|
218
|
+
index, so it lists facets and reports their `facet_source_id`
|
|
219
|
+
- Stop sending `langcode: "en"` in create payloads. Four tools did this. It is
|
|
220
|
+
harmless on a single-language site, but once a second language exists the
|
|
221
|
+
field is writable only with translation permission, so every create failed
|
|
222
|
+
403 "the current user is not allowed to POST the selected field (langcode)" --
|
|
223
|
+
including an ordinary authenticated user creating a term in the site's own
|
|
224
|
+
language. The field is now sent only when the caller asks for a language
|
|
225
|
+
- Fix `drush_command` option rendering, which made every confirmation-answering
|
|
226
|
+
call fail. Boolean options were emitted as `--key=value`, but drush flags take
|
|
227
|
+
no value, so `options={"yes": True}` -- the documented way to install a module
|
|
228
|
+
-- was rejected with "The --yes option does not accept a value" and nothing
|
|
229
|
+
was installed while the tool reported success. A `False` was worse: it emitted
|
|
230
|
+
the bare flag, so asking *not* to confirm silently suppressed confirmation on
|
|
231
|
+
destructive commands. Zero-valued options were also mangled, `--count=0`
|
|
232
|
+
becoming a bare `--count`. Booleans are now bare flags, `False` omits the
|
|
233
|
+
option, and everything else keeps its value
|
|
234
|
+
- Return an `error` from `drush_command` when Drush exits non-zero. It handed
|
|
235
|
+
back the raw result with no `error` key, so the generic escape hatch -- the
|
|
236
|
+
tool an agent reaches for first -- still presented a failed command as an
|
|
237
|
+
empty success, even though `ensure_dict` already fixed this for the 224
|
|
238
|
+
specialised call sites
|
|
239
|
+
- Split several packages in `composer_require`. They arrived as one argv token,
|
|
240
|
+
so composer read `drupal/pathauto` as a version constraint on `drupal/token`
|
|
241
|
+
and refused the whole request with "Could not parse version constraint"
|
|
242
|
+
- Ask for `view`, not `views_view`: the entity type has been `view` since
|
|
243
|
+
Drupal 8, so all six views tools 404'd on a site with Views installed and
|
|
244
|
+
healthy, and read as "module not installed"
|
|
245
|
+
- Ask for `content_moderation_state`, not `moderation_state`, in the moderation
|
|
246
|
+
tools. The node *field* is still called `moderation_state` and is unchanged
|
|
247
|
+
- Ask for `facets_facet`, not `search_api_facet`, in the facet tools
|
|
248
|
+
- Rewrite `config_translation_list` and `config_translation_add`, which targeted
|
|
249
|
+
three mechanisms that do not exist between them: a `config_translation`
|
|
250
|
+
JSON:API resource, a `config:translation:add` Drush command (Drush has no
|
|
251
|
+
translation command category at all), and `$entity->addTranslation()` on a
|
|
252
|
+
config entity, which implements no `TranslatableInterface`. The list reports
|
|
253
|
+
the mappers this site has. The add resolves `{entity_type}/{entity_id}`
|
|
254
|
+
through the config translation mapper to the config object the entity is
|
|
255
|
+
stored as -- `node_type` and `article` become `node.type.article` -- and
|
|
256
|
+
writes a language config override for the target language, which is where
|
|
257
|
+
Drupal keeps config translations. Verified live: the French `name` and
|
|
258
|
+
`description` land on the override while the base config keeps its own
|
|
259
|
+
- Drop a dead `get_resource("jsonapi")` call from `nextjs_helpers_generate`. It
|
|
260
|
+
requested `/jsonapi/jsonapi`, a path that does not exist, and the 404 aborted
|
|
261
|
+
the whole tool for a value the generator never used
|
|
262
|
+
|
|
263
|
+
- Implement the six `layout_builder_*` tools against the real API; they asked
|
|
264
|
+
JSON:API for a resource that has never existed and returned an opaque 404 on
|
|
265
|
+
every call, so reading, writing and reverting layouts now go through Drush to
|
|
266
|
+
`entity_view_display` and `OverridesSectionStorage`
|
|
267
|
+
- Fix `DrushClient._flatten` shlex-splitting list arguments, which made
|
|
268
|
+
`db_tables`, `db_query`, `db_count`, `drush_php_eval` and every `ev` tool fail
|
|
269
|
+
with "Too many arguments"
|
|
270
|
+
- Report a failed Drush run as an `error`: `ensure_dict` passed
|
|
271
|
+
`{returncode: 1, data: {}, stderr}` through untouched, byte for byte an empty
|
|
272
|
+
success, so 224 call sites presented broken commands as empty results
|
|
273
|
+
- Return real tool schemas from `/mcp/tools/list`, which reported
|
|
274
|
+
`input_schema: {}` for all 366 tools
|
|
275
|
+
- Fix `cron_list` (called Drupal 7's `drupal_get_cron_jobs()`, removed in
|
|
276
|
+
Drupal 8), `queue_create` (a shell escape in PHP that never passes through a
|
|
277
|
+
shell, so PHP died with a ParseError) and `queue_delete` (a second argument on
|
|
278
|
+
all three paths when `queue:delete` accepts exactly one)
|
|
279
|
+
- Route `queue_create` and `queue_delete` around `drush queue:delete`, which
|
|
280
|
+
resolves only registered queue services and answered "Queue not found" for a
|
|
281
|
+
queue this server had just created
|
|
282
|
+
- Fix `permission_audit` and `menu_link_list` filtering on fields Drupal does
|
|
283
|
+
not expose (`filter[name]` on `user_role`, `filter[menu]` on
|
|
284
|
+
`menu_link_content`), a 400 on every call on every site
|
|
285
|
+
- Validate whole statements in `db_query`; a `SELECT 1; DROP TABLE x` passed the
|
|
286
|
+
`startswith("SELECT")` guard and dropped the table
|
|
287
|
+
- Stop stacking log handlers on repeated `setup_logging` calls
|
|
288
|
+
|
|
289
|
+
### Removed
|
|
290
|
+
|
|
291
|
+
- Remove dead register_all_tools function from tools/__init__.py
|
|
292
|
+
- Remove duplicate _find_tool_path definition in security_audit.py
|
|
293
|
+
|
|
294
|
+
## [0.1.0] - 2026-10-13
|
|
295
|
+
|
|
296
|
+
### Added
|
|
297
|
+
|
|
298
|
+
- Initial release with 35+ MCP tools across all major Drupal categories
|
|
299
|
+
- FastMCP server with stdio and HTTP transports
|
|
300
|
+
- JSON:API and GraphQL clients with authentication
|
|
301
|
+
- Drush command execution via subprocess
|
|
302
|
+
- Multi-provider AI support (Ollama, OpenAI, Anthropic, NVIDIA NIM)
|
|
303
|
+
- Content management, user management, taxonomy, views, paragraphs tools
|
|
304
|
+
- DevOps tools for DDEV/Lando, Composer, database queries
|
|
305
|
+
- Migration tools for D7 to D10 upgrades
|
|
306
|
+
- Security audit and code quality tools
|