drupal-mcp-connector 0.6.1
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.
- package/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/config/config.example.json +122 -0
- package/package.json +70 -0
- package/src/index.js +499 -0
- package/src/lib/backends/backend-interface.js +164 -0
- package/src/lib/backends/errors.js +31 -0
- package/src/lib/backends/graphql-filter.js +99 -0
- package/src/lib/backends/graphql-names.js +63 -0
- package/src/lib/backends/graphql-normalize.js +73 -0
- package/src/lib/backends/graphql-query.js +129 -0
- package/src/lib/backends/graphql-schema.js +226 -0
- package/src/lib/backends/graphql.js +391 -0
- package/src/lib/backends/index.js +128 -0
- package/src/lib/backends/jsonapi.js +403 -0
- package/src/lib/canonical.js +68 -0
- package/src/lib/config.js +257 -0
- package/src/lib/drupal-fetch.js +144 -0
- package/src/lib/errors.js +38 -0
- package/src/lib/http-auth.js +27 -0
- package/src/lib/oauth.js +177 -0
- package/src/lib/reports-support.js +75 -0
- package/src/lib/security.js +475 -0
- package/src/lib/validate.js +225 -0
- package/src/tools/drush.js +463 -0
- package/src/tools/entities.js +262 -0
- package/src/tools/graphql.js +175 -0
- package/src/tools/media.js +297 -0
- package/src/tools/nodes.js +247 -0
- package/src/tools/reports.js +609 -0
- package/src/tools/site.js +87 -0
- package/src/tools/taxonomy.js +202 -0
- package/src/tools/users.js +250 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
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.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.6.1] - 2026-06-04
|
|
11
|
+
|
|
12
|
+
First release published to npm.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- HTTPS transport: import `randomUUID` from `node:crypto` for session IDs instead of
|
|
16
|
+
relying on the bare `crypto` global, which is not available unflagged on Node 18
|
|
17
|
+
(the minimum supported version).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Comprehensive inline-documentation pass (JSDoc on all exported functions/classes,
|
|
21
|
+
canonical descriptor/entity typedefs) and a Node coding-standards audit across `src/`.
|
|
22
|
+
|
|
23
|
+
## [0.6.0] - 2026-06-03
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Renamed the package to `drupal-mcp-connector` — clearer that it is the MCP↔Drupal connector, and avoids confusion with the Drupal `mcp_server` module. The outbound identity header is now `X-MCP-Client: drupal-mcp-connector/<version>`.
|
|
27
|
+
- Prepared for npm publication (`bin`, `files`, `keywords`).
|
|
28
|
+
|
|
29
|
+
## [0.5.0] - 2026-06-03
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- **OAuth2 write-plane authentication.** Per-site `oauth` block enabling the
|
|
33
|
+
`client_credentials` grant against Drupal `simple_oauth`: token acquisition,
|
|
34
|
+
in-memory per-site caching with silent re-acquire (60s expiry skew), refresh-token
|
|
35
|
+
grant with fallback to `client_credentials`, concurrent-acquire de-duplication, and
|
|
36
|
+
a one-shot token clear + retry on `401`. The client secret is sourced from an
|
|
37
|
+
environment variable (`oauth.clientSecretEnv`) and is never stored in config or
|
|
38
|
+
surfaced in errors.
|
|
39
|
+
- **`write-plane` security preset** mirroring the recommended server-side governance
|
|
40
|
+
profile: writes enabled, no deletes, no GraphQL mutations, entity access limited to
|
|
41
|
+
`node`/`taxonomy_term`/`media`, `user` entities denied, `pass`/`mail` redacted.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- The three fetch helpers resolve auth via an async path so OAuth sites attach a
|
|
45
|
+
freshly-managed Bearer token; static token / Basic-auth sites are unchanged.
|
|
46
|
+
- `requireSecureAuth` now accepts a valid `oauth` block as satisfying the Bearer
|
|
47
|
+
requirement.
|
|
48
|
+
|
|
49
|
+
## [0.4.0] - 2026-06-01
|
|
50
|
+
|
|
51
|
+
The connector is now **dual-protocol**: every tool runs against an abstract backend
|
|
52
|
+
(JSON:API or GraphQL), selectable per site, with one canonical entity shape across both.
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- **Dual-protocol backend layer.** A per-site `api` selector (`"jsonapi"`, `"graphql"`,
|
|
56
|
+
or a priority array; omit to auto-detect) routes every tool through `resolveBackend`.
|
|
57
|
+
JSON:API and GraphQL backends are interchangeable.
|
|
58
|
+
- **GraphQL backend** via [GraphQL Compose](https://www.drupal.org/project/graphql_compose):
|
|
59
|
+
introspection-driven, type-aware field selection (`DateTime`/`Language`/`TextSummary`
|
|
60
|
+
scalar wrappers, entity-reference unions), native sort for `created`/`changed`/`title`,
|
|
61
|
+
and client-side filtering over a bounded fetch (results flagged `approximate`/`truncated`).
|
|
62
|
+
- **Canonical entity shape** (`{ id, entityType, bundle, title, status, langcode, created,
|
|
63
|
+
changed, url, fields, relationships, _backend }`) produced by both backends.
|
|
64
|
+
- **Backend capability model** (`read`/`write`/`delete`/`count`/`filter`/`sort`/`revisions`).
|
|
65
|
+
Writes against a read-only backend raise a clear `BackendCapabilityError`.
|
|
66
|
+
- **Security hardening (all optional, safe defaults):** `X-MCP-Client` identity header
|
|
67
|
+
(override/disable via `MCP_CLIENT_ID`), bearer-authenticated HTTPS transport
|
|
68
|
+
(`MCP_AUTH_TOKEN`), bind-address restriction (`MCP_BIND_HOST`), tokens-from-env per site
|
|
69
|
+
(`apiTokenEnv`), and strict per-site auth enforcement (`requireSecureAuth`).
|
|
70
|
+
- **GraphQL mutation gate:** parser-based detection rejects any mutation when
|
|
71
|
+
`allowGraphqlMutations` is off, regardless of where it appears in the document.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- All 66 tools and 10 reports migrated to the backend layer and canonical output.
|
|
75
|
+
- HTTPS transport hardened: HTTPS mandatory, plain HTTP refused off-localhost unless
|
|
76
|
+
`MCP_ALLOW_HTTP=1`; loopback-only bind without TLS; security headers on every response.
|
|
77
|
+
- Documentation rewritten for the dual-protocol model, canonical output, capability gating,
|
|
78
|
+
and the optional hardening controls.
|
|
79
|
+
|
|
80
|
+
### Removed
|
|
81
|
+
- Bundled `drupal-module/` reference scaffold. Server-side governance now lives in the
|
|
82
|
+
companion [MCP Sentinel](https://www.drupal.org/project/mcp_sentinel) module
|
|
83
|
+
(`drupal/mcp_sentinel`), which supersedes it.
|
|
84
|
+
|
|
85
|
+
### Security
|
|
86
|
+
- Field-level PII redaction applied to canonical entities and JSON:API resources alike.
|
|
87
|
+
- User tools gained explicit PII-access assertions.
|
|
88
|
+
- Whole tree lint-clean (`npm run lint`) with object-injection sinks rewritten to safe lookups.
|
|
89
|
+
|
|
90
|
+
[0.6.0]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.6.0
|
|
91
|
+
[0.5.0]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.5.0
|
|
92
|
+
[0.4.0]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.4.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jeremy Michael Cerda and Wilkes & Liberty, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# drupal-mcp-connector
|
|
2
|
+
|
|
3
|
+
> A secure, multi-site Model Context Protocol (MCP) connector for Drupal — dual-protocol JSON:API and GraphQL access, governed content tools, audit reports, and an SSH Drush bridge.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://drupal.org)
|
|
8
|
+
[](https://modelcontextprotocol.io)
|
|
9
|
+
|
|
10
|
+
Created by **Jeremy Michael Cerda**. Built and maintained by [Wilkes & Liberty, LLC](https://github.com/Wilkes-Liberty).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What It Does
|
|
15
|
+
|
|
16
|
+
`drupal-mcp-connector` connects any [Model Context Protocol](https://modelcontextprotocol.io) client to one or more Drupal sites. It exposes Drupal content and configuration as a set of MCP **tools**, **resources**, and **prompts**, so an MCP client can read, audit, and (where permitted) write content through structured, governed operations instead of the admin UI:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
"Find all articles missing a meta description and list them."
|
|
20
|
+
"Show me every user account that hasn't logged in for 90 days."
|
|
21
|
+
"Create 10 draft product nodes from this structured data."
|
|
22
|
+
"Run an SEO and accessibility audit on the article content type."
|
|
23
|
+
"What content types exist on the site and which are barely used?"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The connector speaks **two Drupal backends interchangeably** — Drupal core's **JSON:API** and **GraphQL** (via [GraphQL Compose](https://www.drupal.org/project/graphql_compose)) — selectable per site. It normalizes both into one canonical entity shape, so the same tools work whether a site exposes JSON:API, GraphQL, or both. An optional SSH **Drush bridge** adds administrative operations the HTTP APIs can't reach.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Dual-Protocol Backends
|
|
31
|
+
|
|
32
|
+
Each site declares which backend(s) it exposes via the `api` key:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
"sites": {
|
|
36
|
+
"main": { "baseUrl": "https://example.com", "api": "jsonapi" },
|
|
37
|
+
"graphql_only": { "baseUrl": "https://api.example.com", "api": "graphql" },
|
|
38
|
+
"either": { "baseUrl": "https://example.com", "api": ["graphql", "jsonapi"] }
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **`api` accepts** `"jsonapi"`, `"graphql"`, or a priority array like `["graphql","jsonapi"]`. Omit it to **auto-detect** (the connector probes both once and caches the result).
|
|
43
|
+
- **One canonical shape.** Both backends return entities as
|
|
44
|
+
`{ id, entityType, bundle, title, status, langcode, created, changed, url, fields, relationships, _backend }`, so tool output is identical regardless of protocol.
|
|
45
|
+
- **Capability-aware.** Each backend advertises what it supports (read, write, delete, server-side filter/sort, revisions). GraphQL via GraphQL Compose is **read-only** (no mutations) and has no server-side field filter, so filters are applied client-side over a bounded fetch and flagged `approximate`/`truncated`. Write tools against a read-only backend return a clear capability error rather than failing silently.
|
|
46
|
+
- **Writes go through JSON:API.** Use a JSON:API-enabled site as the write plane; keep GraphQL as a read plane where that suits your architecture.
|
|
47
|
+
|
|
48
|
+
See **[docs/architecture.md](docs/architecture.md)** for the backend abstraction and **[docs/graphql-local-setup.md](docs/graphql-local-setup.md)** for the GraphQL specifics.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
### 66 Tools Across 9 Modules
|
|
55
|
+
|
|
56
|
+
| Module | Tools |
|
|
57
|
+
|--------|-------|
|
|
58
|
+
| **Nodes** | CRUD for any content type with arbitrary field support |
|
|
59
|
+
| **Taxonomy** | Vocabulary listing + full term CRUD |
|
|
60
|
+
| **Users** | List, get, create, update, block/unblock, role management (PII-gated) |
|
|
61
|
+
| **Media** | List types, CRUD, file upload, orphaned-media detection |
|
|
62
|
+
| **GraphQL** | Execute a query, schema introspection (mutation-gated) |
|
|
63
|
+
| **Entities** | Generic CRUD for *any* Drupal entity type (paragraphs, commerce, webforms, …) |
|
|
64
|
+
| **Site** | Site info, content-type discovery, configured-site listing |
|
|
65
|
+
| **Reports** | Content summary, stale content, field completeness, SEO/accessibility audits, taxonomy usage, user activity, revision hotspots (10 read-only reports) |
|
|
66
|
+
| **Drush** | Cache rebuild, cron, config sync, module management, DB updates via SSH |
|
|
67
|
+
|
|
68
|
+
### MCP Resources
|
|
69
|
+
Browsable, always-fresh context the client can read without calling a tool:
|
|
70
|
+
- **`drupal://sites`** — configured site profiles (no credentials)
|
|
71
|
+
- **`drupal://{site}/content-types`** — content types with field schemas
|
|
72
|
+
- **`drupal://{site}/security-policy`** — the active security configuration
|
|
73
|
+
|
|
74
|
+
### MCP Prompts
|
|
75
|
+
Workflow templates usable as slash-commands from any MCP client:
|
|
76
|
+
- `drupal-content-audit` — walk through a full site content audit
|
|
77
|
+
- `drupal-create-article` — guided article creation with all fields
|
|
78
|
+
- `drupal-seo-fix` — find and fix SEO gaps
|
|
79
|
+
- `drupal-user-cleanup` — identify and handle inactive accounts
|
|
80
|
+
|
|
81
|
+
### Security Model
|
|
82
|
+
Defense-in-depth with four one-line presets, enforced connector-side and complemented by Drupal-side governance:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
"security": { "preset": "auditor" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Preset | What it does |
|
|
89
|
+
|--------|-------------|
|
|
90
|
+
| `development` | Everything allowed — local development only |
|
|
91
|
+
| `content-editor` | Create/edit nodes, media, terms; no deletes; no user access |
|
|
92
|
+
| `auditor` | Read-only, all entity types, PII fields redacted |
|
|
93
|
+
| `production-strict` | Read-only, no user entities, broad PII redaction |
|
|
94
|
+
|
|
95
|
+
Presets layer with entity allow/deny lists, per-bundle operation rules, and field-level redaction. Optional transport hardening (bearer-authenticated HTTPS, bind-address restriction, secrets-from-env) is covered in **[docs/security-hardening.md](docs/security-hardening.md)**.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- **Node.js** 18+
|
|
102
|
+
- **Drupal** 10 or 11 (JSON:API ships in core)
|
|
103
|
+
- For the **GraphQL backend**: [GraphQL Compose](https://www.drupal.org/project/graphql_compose)
|
|
104
|
+
- For **token auth** (recommended): [Simple OAuth](https://www.drupal.org/project/simple_oauth)
|
|
105
|
+
- For the **Drush bridge**: SSH key access to the Drupal server
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Quick Start
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 1. Clone and install
|
|
113
|
+
git clone https://github.com/Wilkes-Liberty/drupal-mcp-connector
|
|
114
|
+
cd drupal-mcp-connector
|
|
115
|
+
npm install
|
|
116
|
+
|
|
117
|
+
# 2. Configure
|
|
118
|
+
cp config/config.example.json config/config.json
|
|
119
|
+
# Edit config/config.json — add your site's baseUrl, api backend, and auth
|
|
120
|
+
|
|
121
|
+
# 3. Run (stdio transport)
|
|
122
|
+
node src/index.js
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Register with an MCP client
|
|
126
|
+
|
|
127
|
+
Most desktop and CLI MCP clients launch the connector over **stdio**. Add an entry to your client's MCP server configuration:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"mcpServers": {
|
|
132
|
+
"drupal": {
|
|
133
|
+
"command": "node",
|
|
134
|
+
"args": ["/absolute/path/to/drupal-mcp-connector/src/index.js"],
|
|
135
|
+
"env": {
|
|
136
|
+
"DRUPAL_BASE_URL": "https://mysite.com",
|
|
137
|
+
"DRUPAL_API_TOKEN": "your-token-here"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
For multi-client or remote use, run the HTTPS transport and register the endpoint instead — see **[docs/getting-started.md](docs/getting-started.md)**.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Companion Drupal Module — MCP Sentinel
|
|
149
|
+
|
|
150
|
+
The connector works out of the box against Drupal core's JSON:API and a GraphQL Compose schema. For server-side governance, pair it with the **[MCP Sentinel](https://www.drupal.org/project/mcp_sentinel)** module (`drupal/mcp_sentinel`), which enforces policy *inside* Drupal — independent of any connector configuration:
|
|
151
|
+
|
|
152
|
+
- Role-bound policy profiles (operation gates, entity allow/deny, field redaction)
|
|
153
|
+
- Tamper-evident audit log of every governed MCP operation, attributed to the acting account
|
|
154
|
+
- Content locks that prevent edits to content a human is actively editing
|
|
155
|
+
- OAuth scope enforcement (`mcp:read` / `mcp:write`) per tool
|
|
156
|
+
- HMAC-signed webhooks on MCP-driven entity changes
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
composer require drupal/mcp_sentinel drupal/mcp_server drupal/simple_oauth
|
|
160
|
+
drush en mcp_sentinel mcp_sentinel_server mcp_server_tool_bridge -y
|
|
161
|
+
drush mcp-sentinel:setup
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Governance keys off the authenticated account's role and OAuth scopes — not request headers. The connector sends an `X-MCP-Client` identity header purely as a log label. See the [MCP Sentinel project page](https://www.drupal.org/project/mcp_sentinel) for the full contract.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Documentation
|
|
169
|
+
|
|
170
|
+
| Doc | Description |
|
|
171
|
+
|-----|-------------|
|
|
172
|
+
| [Getting Started](docs/getting-started.md) | Full setup: DDEV/Lando, Simple OAuth, multi-site, transports |
|
|
173
|
+
| [Architecture](docs/architecture.md) | Backend abstraction, canonical model, and how to extend it |
|
|
174
|
+
| [GraphQL Setup](docs/graphql-local-setup.md) | GraphQL Compose backend + local TLS notes |
|
|
175
|
+
| [Tools Reference](docs/tools-reference.md) | Full reference for all 66 tools |
|
|
176
|
+
| [Security Guide](docs/security.md) | Presets, entity access control, field redaction |
|
|
177
|
+
| [Security Hardening](docs/security-hardening.md) | Optional transport, identity, and secrets controls |
|
|
178
|
+
| [Integration Contract](docs/integration-contract.md) | The connector ↔ Drupal-governance contract (identity, OAuth scopes, compatibility) |
|
|
179
|
+
| [Whitepaper](docs/whitepaper.md) | Vision, personas, and use cases |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
186
|
+
|
|
187
|
+
## Security
|
|
188
|
+
|
|
189
|
+
Found a vulnerability? See [SECURITY.md](SECURITY.md). Please do not open a public issue.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
[MIT](LICENSE) © 2026 Jeremy Michael Cerda and [Wilkes & Liberty, LLC](https://github.com/Wilkes-Liberty)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy to config/config.json and fill in your values. This file is safe to commit. config.json is gitignored.",
|
|
3
|
+
|
|
4
|
+
"_api_modes": {
|
|
5
|
+
"_comment": "Per-site 'api' selects the backend: 'graphql' | 'jsonapi' | a priority array like ['graphql','jsonapi']. Omit to auto-detect (probes both once). GraphQL is read-only (no mutations); writes require a JSON:API site.",
|
|
6
|
+
"examples": ["jsonapi", "graphql", ["graphql", "jsonapi"]]
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"_security_options": {
|
|
10
|
+
"_comment": "All optional. apiTokenEnv: read the Bearer token from this env var instead of apiToken (keeps secrets out of the config file). requireSecureAuth: reject anon/basic, require HTTPS+Bearer (recommended for production). Env overrides: MCP_CLIENT_ID overrides or disables the outbound identity header; MCP_AUTH_TOKEN requires bearer auth on the HTTPS /mcp endpoint; MCP_BIND_HOST restricts the listen interface (with TLS). See docs/security-hardening.md."
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
"defaultSite": "production",
|
|
14
|
+
|
|
15
|
+
"tls": {
|
|
16
|
+
"_comment": "TLS is required for the HTTPS transport (MCP_TRANSPORT=https). Ignored in stdio mode.",
|
|
17
|
+
"certPath": "/etc/ssl/certs/drupal-mcp.crt",
|
|
18
|
+
"keyPath": "/etc/ssl/private/drupal-mcp.key",
|
|
19
|
+
"port": 3443
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"sites": {
|
|
23
|
+
"production": {
|
|
24
|
+
"baseUrl": "https://mysite.com",
|
|
25
|
+
"apiToken": "eyJhbGciOiJSUzI1NiJ9...",
|
|
26
|
+
"apiTokenEnv": "DRUPAL_TOKEN_PRODUCTION",
|
|
27
|
+
"requireSecureAuth": true,
|
|
28
|
+
"username": "",
|
|
29
|
+
"password": "",
|
|
30
|
+
"graphqlEndpoint": "/graphql",
|
|
31
|
+
"api": "jsonapi",
|
|
32
|
+
|
|
33
|
+
"drushSsh": {
|
|
34
|
+
"_comment": "Optional. Enables drupal_drush_* tools. SSH key auth only — no passwords.",
|
|
35
|
+
"host": "ssh.mysite.com",
|
|
36
|
+
"user": "deploy",
|
|
37
|
+
"keyPath": "~/.ssh/id_ed25519",
|
|
38
|
+
"drupalRoot": "/var/www/html/web",
|
|
39
|
+
"port": 22
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
"security": {
|
|
43
|
+
"_comment": "Presets: development | content-editor | auditor | production-strict",
|
|
44
|
+
"preset": "auditor",
|
|
45
|
+
"readOnly": true,
|
|
46
|
+
"allowDestructive": false,
|
|
47
|
+
"allowGraphqlMutations": false,
|
|
48
|
+
"allowedEntityTypes": null,
|
|
49
|
+
"deniedEntityTypes": ["user"],
|
|
50
|
+
"globalRedactedFields": ["pass", "mail", "field_api_key", "field_private"],
|
|
51
|
+
"entityRules": {
|
|
52
|
+
"node": {
|
|
53
|
+
"allowedOperations": ["read"],
|
|
54
|
+
"deniedBundles": ["private_document", "internal_memo"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
"staging": {
|
|
61
|
+
"baseUrl": "https://staging.mysite.com",
|
|
62
|
+
"username": "api-user",
|
|
63
|
+
"password": "change-me-use-token-instead",
|
|
64
|
+
"api": "jsonapi",
|
|
65
|
+
"security": { "preset": "content-editor" }
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
"local": {
|
|
69
|
+
"_comment": "Plain HTTP is allowed for localhost targets only. A warning is logged.",
|
|
70
|
+
"baseUrl": "http://mysite.lndo.site",
|
|
71
|
+
"username": "admin",
|
|
72
|
+
"password": "admin",
|
|
73
|
+
"security": { "preset": "development" }
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
"graphql_only": {
|
|
77
|
+
"_comment": "A site that exposes GraphQL only (JSON:API disabled). GraphQL is read-only.",
|
|
78
|
+
"baseUrl": "https://api.example.com",
|
|
79
|
+
"graphqlEndpoint": "/graphql",
|
|
80
|
+
"api": "graphql",
|
|
81
|
+
"security": { "preset": "auditor" }
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"oauth_write_plane": {
|
|
85
|
+
"_comment": "OAuth2 client_credentials grant against simple_oauth. The client secret is read from the named env var and never stored here.",
|
|
86
|
+
"baseUrl": "https://api.example.com",
|
|
87
|
+
"requireSecureAuth": true,
|
|
88
|
+
"api": "jsonapi",
|
|
89
|
+
"oauth": {
|
|
90
|
+
"tokenUrl": "/oauth/token",
|
|
91
|
+
"clientId": "mcp-agent-prod",
|
|
92
|
+
"clientSecretEnv": "MCP_AGENT_CLIENT_SECRET",
|
|
93
|
+
"scopes": ["mcp:read", "mcp:write"],
|
|
94
|
+
"grant": "client_credentials"
|
|
95
|
+
},
|
|
96
|
+
"security": { "preset": "write-plane" }
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
"_security_presets": {
|
|
101
|
+
"development": "All operations allowed. Local dev only.",
|
|
102
|
+
"content-editor": "Create/edit nodes+media+terms. No deletes. No user entity access.",
|
|
103
|
+
"auditor": "Read-only. All entity types. User PII fields redacted.",
|
|
104
|
+
"production-strict": "Read-only. No user entities. Broad PII redaction.",
|
|
105
|
+
"write-plane": "Governed writes (no delete/mutations) on node, taxonomy_term, media. No user. Redacts pass/mail."
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
"_mcp_client_registration": {
|
|
109
|
+
"_comment": "Add under your MCP client's mcpServers config (stdio transport)",
|
|
110
|
+
"drupal": {
|
|
111
|
+
"command": "node",
|
|
112
|
+
"args": ["/absolute/path/to/drupal-mcp-connector/src/index.js"]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
"_https_transport_registration": {
|
|
117
|
+
"_comment": "For multi-client HTTP mode — register the HTTPS endpoint instead",
|
|
118
|
+
"drupal": {
|
|
119
|
+
"url": "https://your-server:3443/mcp"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "drupal-mcp-connector",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "A secure, multi-site Model Context Protocol (MCP) connector for Drupal — dual-protocol JSON:API and GraphQL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"drupal-mcp-connector": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"config/config.example.json",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"drupal",
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"jsonapi",
|
|
22
|
+
"json-api",
|
|
23
|
+
"graphql",
|
|
24
|
+
"connector",
|
|
25
|
+
"headless",
|
|
26
|
+
"decoupled",
|
|
27
|
+
"drush"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://github.com/Wilkes-Liberty/drupal-mcp-connector",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/Wilkes-Liberty/drupal-mcp-connector.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/Wilkes-Liberty/drupal-mcp-connector/issues"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"author": "Jeremy Michael Cerda <jmcerda@wilkesliberty.com> (https://wilkesliberty.com)",
|
|
39
|
+
"contributors": [
|
|
40
|
+
"Wilkes & Liberty, LLC <opensource@wilkesliberty.com> (https://wilkesliberty.com)"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"start": "node src/index.js",
|
|
47
|
+
"start:https": "MCP_TRANSPORT=https node src/index.js",
|
|
48
|
+
"start:dev": "MCP_TRANSPORT=https MCP_ALLOW_HTTP=1 MCP_PORT=3443 node src/index.js",
|
|
49
|
+
"lint": "eslint src/",
|
|
50
|
+
"lint:fix": "eslint src/ --fix",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"audit": "npm audit --audit-level=high",
|
|
54
|
+
"check": "npm run lint && npm run audit",
|
|
55
|
+
"syntax-check": "for f in src/lib/*.js src/tools/*.js src/index.js; do node --input-type=module --check < $f && echo \"$f ✓\"; done"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
59
|
+
"graphql": "^16.9.0",
|
|
60
|
+
"node-fetch": "^3.3.2",
|
|
61
|
+
"ssh2": "^1.16.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"eslint": "^9.0.0",
|
|
65
|
+
"eslint-plugin-security": "^3.0.0",
|
|
66
|
+
"eslint-plugin-n": "^17.0.0",
|
|
67
|
+
"globals": "^15.0.0",
|
|
68
|
+
"vitest": "^2.1.0"
|
|
69
|
+
}
|
|
70
|
+
}
|