4GROW Marketing Data MCP
Provides read-only tools to interact with a Drupal site's content API, including listing content types, retrieving content type schemas, fetching content, and searching content.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@4GROW Marketing Data MCPList all content types available on the site."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
4GROW Marketing Data MCP
Thin, standalone MCP adapter. It exposes six read tools backed by the Drupal Content API:
list_content_types,get_content_type_schema,get_content,get_content_revisions,get_content_revision,search_content.
When MCP_WRITE_ENABLED=true, it additionally exposes two restricted tools:
preview_training_update,commit_training_update.
WRITE is limited to title, meta_title, and meta_description on
npxtraining. Preview never saves. Commit accepts only the one-time preview
token and explicit confirmation; Drupal remains the authoritative permission,
allowlist, revision-locking, and audit layer.
The Drupal allowlist currently exposes npxtraining, landing_page and
npxquiz. Quiz structure is read with expand on field_questions and
field_questions.field_answers. Scoring, hints that reveal the key,
participant records, and npx_test entities are omitted. Expanding a quiz
does not solve it and does not load attempt results.
The Drupal envelope and the six tool payloads are documented in the Drupal
repo file instrukcje-zadan/drupal-chat-integration/GROW-1049_READ_API_CONTRACT.md.
Success is { data }. Failures are { error: { status, code, message } }.
code is one of not_found, unknown_field, unsupported_type,
access_denied, invalid_request, response_too_large, busy, or
timeout (MCP-only, when Drupal does not answer in time). Tool errors include that code in the text.
On response_too_large, retry with fewer fields or a lower limit. On busy,
wait and retry once.
A field present with null is an empty stored value. A field absent from
fields was omitted because the account cannot view it or it was not
selected. Unexpanded references stay as { target_id } without nested
fields. Failed expansions add expansion_status: unavailable, cycle,
depth_limit, truncated, or forbidden. Search never treats an omitted
field as empty.
Drupal also enforces the same input limits as the tools: at most 50 fields
(20 on a revision listing), 10 search conditions, 50 in values, 500
characters per search value, 10 expand paths, and positive numeric IDs.
Listing completeness
list_content_types returns the whole allowlist. search_content and
get_content_revisions do not. A page that fills limit is still partial
when has_more is true: continue with after_nid / after_revision_id set
to next_after_nid / next_after_revision_id.
scan_limit_reached is the other ending. For search it means this call
stopped after 5000 candidate nodes, so later content was not scanned and a
short or empty page is not proof that nothing remains. For revisions it means
the 250-revision work limit stopped the scan. The list is complete only when
has_more is false. Stopping earlier is a partial result, not every match.
Search inaccessible counts candidates skipped because a filtered field could
not be read; an absent node is then not proof that it fails the filter.
Related MCP server: TrustLayer MCP Server
Local start
Copy
.env.exampleto the ignored.envand provide local credentials, including a long randomMCP_AUTH_TOKEN.Make sure Drupal works at
http://127.0.0.1:8888.Run
npm start.Check
http://127.0.0.1:3000/health.
The MCP endpoint uses stateless Streamable HTTP at
http://127.0.0.1:3000/mcp. Every /mcp request must send:
Authorization: Bearer <MCP_AUTH_TOKEN>
/health stays unauthenticated and does not call Drupal.
In MCP Inspector, use Streamable HTTP, the /mcp URL, and the same Bearer
header.
WRITE is disabled by default. To enable it for testing, set
MCP_WRITE_ENABLED=true and provide DRUPAL_WRITE_USERNAME and
DRUPAL_WRITE_PASSWORD for a separate minimal Drupal account. Never reuse the
READ account or grant the WRITE account administrative permissions.
Local smoke test
With the server running in another terminal, run npm run test:list.
The dynamic schema can be checked with npm run test:schema.
Selected fields of a local training node can be checked with
npm run test:content. Override its default node ID with TEST_CONTENT_NID.
Reading revision history
get_content_revisions returns revision metadata by default. Two options make
it answer questions about how a value changed over time:
fieldsadds what those fields held in each listed revision, at most twenty per call.changes_onlykeeps only the revisions that introduced a new value, and needsfields.
Every item returned with changes_only is the revision that introduced the
value it carries, so its author and timestamp answer who changed it and when.
compared_to_revision_id names the revision holding the previous value, which
makes the boundary unambiguous. Both orders report the same set of changes:
nid 4, fields=[field_npxtraining_price], changes_only=true
reference revision 41388 1990 the newest state read, not a change
change revision 39526 1990 compared_to_revision_id 39458
Monika Krzywicka, 7 Apr 2026, up from 1890The reference is deliberately not an item. It is only the state the
comparisons start from, because nothing older had been compared to it yet, and
treating it as a change would invent an edit that never happened.
inaccessible counts the revisions that could not be read. When it is above
zero, a reported change may in truth have happened in one of the hidden
revisions between the two named ones.
Drupal cannot load part of an entity, so every examined revision costs a full
entity load of roughly 45 ms. One request therefore examines at most 250
revisions and reports how many it saw in examined. When has_more is true,
continue with after_revision_id set to next_after_revision_id; the cursor
always advances, even across a stretch where nothing changed.
scan_limit_reached separates the two ways a call can end. True means the work
limit stopped it and older revisions are still waiting, so a page holding no
changes proves nothing on its own. False alongside a false has_more means the
history really ended. Reading the full 822-revision history of one training
takes four calls of about ten seconds each.
Revision history also needs its own Drupal permission. Being able to read the
current page is not enough: the account needs view <bundle> revisions or
view all revisions, otherwise both revision endpoints answer 403 rather than
pretending the history is empty.
Timeouts
DRUPAL_TIMEOUT_MS caps every outgoing Drupal call. It accepts 1000-120000 and
defaults to 30000. On a timeout the tool returns a normal MCP error result
saying how long it waited, instead of an unexplained failure.
Keep the chain ordered from the outside in, otherwise the wrong layer gives up first and the client only sees a dropped connection:
MCP client > reverse proxy (mcp.4grow.pl) > this server > Drupal
proxy_read_timeout DRUPAL_TIMEOUT_MSDRUPAL_TIMEOUT_MS must stay below the proxy read timeout, which is 60 s by
default in Plesk/Nginx. Local drush runserver has no PHP max_execution_time
cap (0 / unlimited). Production PHP-FPM must stay at or above this Drupal
timeout; that FPM value is NOT VERIFIED here.
search_content and get_content_revisions share a process semaphore of two
concurrent Drupal reads (MCP_HEAVY_CONCURRENCY). Extra calls wait up to
MCP_HEAVY_WAIT_MS (default 10 s), then the tool returns an error instead of
opening a third scan. /mcp is also limited to MCP_RATE_LIMIT_PER_MIN
(default 30) with burst MCP_RATE_BURST (default 10). Drupal and this client
reject JSON larger than MCP_MAX_RESPONSE_BYTES (default 2 MB).
search_content is the expensive tool, because it scans and loads nodes one by
one. Raise the timeout only if searches genuinely need it, and prefer narrowing
conditions and limit first. get_content_revisions with fields is the
next heaviest, which is why its own scan is bounded rather than left to the
timeout.
Deployment
The server host runs released commits from this repository, never copied files. The layout separates code, secrets, and the version currently in use:
/opt/4grow-mcp-releases/<date>-<sha> one git clone per release
/opt/4grow-mcp-shared/.env secrets, outlive every release
/opt/4grow-mcp symlink to the live releasesystemd runs 4grow-mcp.service with WorkingDirectory=/opt/4grow-mcp, so
moving the symlink changes the version and moving it back is a full rollback.
ExecStart calls node directly rather than through npm, so SIGTERM from
systemctl reaches the process and the HTTP server closes gracefully.
Deploy with scripts/deploy.sh [branch] on the host. It clones the branch,
installs production dependencies, boots the release on port 3001 to prove it
starts, and only then repoints the symlink and restarts the service. The live
version keeps serving traffic during every step except the restart itself. If
the release fails its smoke test, the deploy stops and nothing is switched. If
it fails /health after the restart, the symlink returns to the previous
release automatically.
.env is never part of a release. Each release symlinks it from the shared
directory, so credentials and DRUPAL_TIMEOUT_MS survive deploys untouched.
Keep MCP_WRITE_ENABLED=false until the Drupal WRITE account, permission,
audit table, and test content are ready.
Tunnel / ChatGPT
Do not expose /mcp until MCP_AUTH_TOKEN is set. A tunnel hostname must also
be listed in MCP_ALLOWED_HOSTS (comma-separated), because the server still
binds to loopback and validates the Host header.
Official OpenAI split:
Inspector, Codex, and ChatGPT developer-mode Token auth: this Bearer header is enough.
A published ChatGPT plugin needs OAuth 2.1 (protected-resource metadata, authorization server, CIMD/DCR). That is not implemented here yet.
ChatGPT developer mode pulls instructions and tool descriptions from this
server. OpenAI asks that the first 512 characters of instructions stand
alone; ours name the three types, the three read flows, Polish table answers,
and completeness. Refresh the app after deploy so ChatGPT does not keep a
stale tool list.
MCP does not install a ChatGPT Skill and cannot force the model to obey the
instructions. Regression prompts for a live ChatGPT pass are in
prompts/chatgpt-read-regression.md: a data question, an ambiguous question,
a WRITE request, Drupal content that looks like instructions, and a long list.
Default answer shape the instructions ask for: Polish, a NID/title table, and
whether the list is complete or partial. Do not write a report unless asked.
When the user asks for a report, summary, CSV or Excel, ask one short question
which form they want, then build it in the chat from fetched rows. MCP has no
export or document tool. null meta fields are stored overrides, not proof
that HTML tags are missing.
Security boundary
Drupal credentials are read from environment variables and never returned in MCP tool results.
The inbound MCP token is a single application key, not a human identity. Whoever holds it can call every read tool. Keep it on the approved ChatGPT connector only. Drupal Basic auth is a separate service account.
The tool is annotated as read-only and non-destructive. There are six tools and none of them write.
Drupal independently enforces its bundle allowlist, entity access, role, and permission.
Drupal content returned by tools is untrusted data, not model instructions.
The server binds to loopback only during local development.
/mcpaudit logs are one JSON line on stderr:request_id, tool name, duration,ok/error. They never include Authorization, Drupal passwords, or field values./healthstays unauthenticated and does not call Drupal.
This server cannot be deployed
Maintenance
Related MCP Connectors
Public read-only MCP for products, frameworks, guides, methodology, and blog metadata.
Search and retrieve published Alkemata articles, pages, and guidance through a read-only MCP server.
Read-only access to InfluSense influencer discovery, ratings, watchlists, and reports via MCP.
MCP gateway for donhuffines.com: articles, search and full text. Read-only, no auth.
Related MCP Servers
FlicenseAqualityCmaintenanceModel Context Protocol (MCP) integration for managing content in the DevHub CMS system (blog posts, content, location administration)109-- AlicenseNot gradedqualityNot gradedmaintenanceProvides read-only access to TrustLayer's public API, enabling users to query and retrieve data about parties, documents, projects, and other TrustLayer entities through MCP-compatible tools.MIT
- AlicenseDqualityDmaintenanceEnables to access and manage content on microCMS through MCP, supporting listing, retrieval, search, and filtering of API endpoints.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to interact with Drupal sites through MCP tools, with automatic discovery, OAuth-based authentication, and scope validation.12 npm5MIT