marketing-stack
Click on "Install 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., "@marketing-stackHow is the pricing page doing in search?"
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.
marketing-stack
An orchestration layer built on Claude Code and MCP. One entry point, three namespaces, nine tools — over data that previously lived in scattered JSON files, logs and weekly report emails. Anyone using the server needs no knowledge of the directory layout. No SSH, no reading JSON by hand.
Three bodies of data are connected:
Terminology — lookup and prose checking against an approved DE-EN glossary
SEO / GEO — search performance, ranking decay, share of answer in AI responses, opportunities, competitors, change history
Foundation files — binding company facts and the blocklist of impermissible claims, exposed as MCP resources and therefore in effect in every session
The governing rule across the whole server: no figure without its date, and no absence that could be mistaken for a measurement.
At a glance
Protocol | MCP over stdio, JSON-RPC 2.0 |
Implementation |
|
Scope | 3 namespaces, 9 tools, 2 resources |
Client | Claude Code |
Upstream data source | a custom-built SEO/GEO bot (Node.js, process management via PM2, seven independent detectors) integrating Google Search Console, the Webflow CMS API and four LLM APIs |
Write path | none outside the project directory |
Network at runtime | none |
Open ports | none |
Secret access | none — the server never reads |
The API integrations live in the upstream bot, not in this server. At runtime the server calls nothing; it reads local files and formats the result. That separation is deliberate and part of the security model.
Related MCP server: mcp-deepcontext
Context
This repository is an extract from a system running in production, not a sample project. The server runs under Claude Code on an Ubuntu VPS and reads the output of a custom-built SEO/GEO bot that analyses a company website, generates suggestions, and writes them once approved.
What is published is the transferable layer: server logic, tool boundaries, security model, limits. Not published: operational data, installation-specific paths, and the blocklist containing reference customer names — that file is loaded at runtime from outside the repository (see Limits, item 11).
The problem it solves
The upstream bot produces data continuously. Seven detectors write independently into their own files and each sends its own report. No layer brings their results together.
So the question "how is this one page doing?" used to cost: log in, locate the most recent file across several directories, read JSON by hand, search the change log for the last write, assemble the values mentally. Five files for one question — manual work nobody repeats across dozens of pages.
seo_page_visibility consolidates nine signals from five sources, each with its
source and data date, and explicitly names what is not available for that
page. The difference is not convenience but scalability: manual work becomes a
question.
What the server explicitly does not replace: the dashboard and the periodic reports. Those answer questions defined weeks ago. This server answers questions that come up now.
Foundation files as MCP resources
The fact base and the blocklists previously took effect only inside the bot: when generating meta descriptions and during the fact check before every write. A hand-written post, an ad, a slide deck — none of them were covered.
Exposed as MCP resources (facts://company, facts://guardrails) they apply in
every Claude session without anyone pasting them in. The rule set that prevents a
silver partner status from quietly becoming gold, or a certification number from
being invented, now takes effect where the text is actually written — not only in
the one system that knows about it.
The mechanism is a one-way export, not a reimplementation: the bot's source
modules are exported daily as data (JSON, text extract), never loaded via
require. When the source changes, the resource changes on the next run. If the
export fails, the previous file stays in place and the run reports an error —
rather than silently serving an empty list.
Hence the project's hardest rule: the server must never become a second source of truth. Where logic exists in the production system it is mirrored here, not reinvented. Where mirroring is impossible, the gap is named in the output itself (see Limits, item 6).
The architectural decision: mirror, not direct access
The server does not read from the production directories but from a mirrored copy.
┌──────────────────────────────────┐
│ production SEO/GEO bot │ runs as root
│ data/ changes.log lib/ │ seven detectors, writing continuously
└────────────────┬─────────────────┘
│
│ mirror cron, daily 09:00, runs as root
│ rsync (copies) + exports (JS modules → JSON)
▼
┌──────────────────────────────────┐
│ mirror │ owned by the unprivileged user
│ data/ changes.log │
│ companyFacts.json guardrails │
└────────────────┬─────────────────┘
│ read-only
▼
┌──────────────────────────────────┐ ┌──────────────────────────┐
│ marketing-stack (this server) │◀─────┤ terminology glossary │
│ server.js + lib/ │ read │ (JSON) │
└────────────────┬─────────────────┘ └──────────────────────────┘
│ stdio / JSON-RPC 2.0
▼
Claude CodeThe dependency runs strictly one way: the MCP server depends on the bot; the bot does not depend on the MCP server. There is not one line in the bot that refers to this server.
The rejected alternative was direct access to the production directories — always current, no mirror needed. The price would have been an MCP process running with root privileges alongside a production system, and Claude Code having to operate as root. A bug in the server could then have reached production data — even with read-only intent.
The mirror was chosen instead. The production system is therefore structurally untouchable: not because the server code behaves well, but because the process cannot reach those files with its privileges.
The price is data age — up to 24 hours. So that this never goes unnoticed,
every signal states its data date, derived from the filename and falling back to
mtime only when there is no alternative. Past a threshold, every tool response
is prefixed with a staleness banner. A second effect of the same decision: a
detector writing in the middle of a query cannot hand over a half-written file.
Failure behaviour: if the mirror cron stops, data ages and the banners appear. If the bot stops entirely, the server keeps answering from the last mirror, with a banner. If the server fails, the bot does not notice. If the server returns a wrong number, the error stays on the read path — it can cause a bad decision, but not data damage.
Findings
A query layer over scattered data inevitably exposes divergences between documentation and reality. Two of them on the day it was finished:
535 self-contradictions in an approved glossary
A curated corpus of 4,838 terms was considered complete after seven review rounds. This server's checking function found 535 terms held simultaneously as a rejected variant and as an approved equivalent.
The cause is structural and transferable: the automated QA pass checks the glossary against itself in blocks of 30 terms. Across 4,838 entries that is roughly 160 blocks. Two contradictory entries sitting in different blocks are never compared — the probability that two given entries land in the same block is below one percent. A block-wise model call cannot form a corpus-wide set intersection. The human rounds could not find it either, because all of them worked on subsets: each reviewer saw one correct entry and decided correctly. The contradiction exists only in the aggregate view.
This is not an argument against the quality of the curation but for a missing
layer of verification. The server introduced it without that being its purpose —
and deliberately does not resolve the finding: the corpus is a record of
review, and a script that quietly rewrites approved entries makes it impossible
afterwards to distinguish what a human verified from what an automation asserted.
The server classifies, counts and documents it as a property of the data source —
surfaced as the class WIDERSPRUCH (contradiction).
A module path that had survived three revisions of the documentation
The fact base was consistently documented under a path that does not exist; the file sits in a different directory. It surfaced because a mirror command pointed at the documented path and failed silently — precisely the class of error the whole server is built against. A second module, the only consumer of that fact base, appeared in no module overview.
A third finding concerned a locale asymmetry: two visibility signals for the same page pointed in opposite directions. In an aggregate figure both would have been invisible — and that is exactly how they had been reported. Both signals are now separated by language.
Namespaces and tools
Terminology
term_lookup(term, richtung) — Looks up a term. richtung: de-en, en-de
or auto (default; searches both directions, because the glossary holds both DE
and EN source entries). Returns the binding translation, rejected variants,
rationale, and do-not-translate status.
term_check_text(text, modus) — Checks DE/EN prose against the glossary.
Every hit is classified; the order of checks is binding, because each class
implies a different action:
Class | Meaning | What the author can do |
| rejected and approved at once | nothing — this needs a terminology review |
| held only as a rejected variant | replace with the stated equivalent |
| several distinct approved targets | decide by context |
| exactly one target, used correctly | nothing (shown only with |
modus: probleme (default — problem classes only, plus a count of suppressed
hits) or alle.
Deliberately not built in: a minimum-length or stopword filter. It would
silently swallow real findings such as approval or documents. Noise is
reduced through precision only — word boundaries, longest match per position,
consolidation of identical targets — never through suppression. A checking tool
that stays quiet is worse than one that says too much.
term_do_not_translate() — All protected names that must remain unchanged.
SEO / GEO
seo_page_visibility(seite) — The centrepiece. Consolidates nine signals for
one page (slug or substring) from five sources: search performance, impressions
without clicks, decay, current rankings, anomalies, AI impressions (pilot pages
only, split DE/EN), mentions per AI engine with prompt language, meta status and
provenance (bot or manual), and the last write. Every line names its source and
data date; where a signal is missing it reads "no data in <source>".
geo_status(wochen) — Share of answer per AI engine across the last N runs,
separated by language (DE / EN / overall), trend against the previous run, prompts
won and lost.
seo_opportunities(typ) — Opportunities by type (striking_distance,
impressions_no_clicks, decay), sorted by an explicitly named field, top 15
with the total count stated.
geo_competitors(name) — Competitors appearing in AI answers, with trend.
Without name: all competitors in the latest run by frequency. With name: a
time series across all runs.
seo_change_history(seite) — What the bot wrote, when, and on which page.
Without seite: the last 20 changes.
Foundation files
facts_for_url(url) — Maps a URL to a business unit (substring match on
product abbreviations, first match wins; no match → null, and then no
facts). Returns the matched abbreviation as matchedProduct so that a false
positive becomes visible instead of invisible.
Resources: facts://company (unit facts and usage rules) and
facts://guardrails (prohibited claims).
Security model
Guardrail | Implementation |
Strictly read-only | No write outside the project directory. Source files are opened for reading only. |
No guessed paths or field names | Every file was read before code processed it. The basis is a preceding schema survey, not an assumption about structure. |
No secrets | No access to |
No network calls at runtime | The server reads local files. No outbound calls. |
No execution of bot code | Fact base and blocklists are exported as data, never loaded via |
Privilege separation | What root owns stays with root. The server process cannot reach the production files. |
Registration in Claude Code
Server entry in the project
.mcp.json(start command plus environment variables). Template:.mcp.json.example.Approval via
enabledMcpjsonServersin the user settings — otherwise a project server stays permanently "pending approval".Binding to the start command via
allowedMcpServersusingserverCommand, notserverName.
Item 3 matters most: serverName is a freely chosen label and not a security
control. Anyone who knows the name can assign it. Only binding to the concrete
serverCommand guarantees that the vetted start command is what actually runs.
Approval in the user settings remains self-imposed: whoever set it can loosen it.
Real enforcement only exists once the rule sits somewhere the executing user
cannot reach — a managed policy with allowManagedMcpServersOnly. Allowlists
from user, project and local settings are then ignored, while denylists continue
to apply from all sources. For single-user operation this is not operationally
necessary; it turns "I abide by my own allowlist" into "policy and execution are
separate".
Limits
This is the most important section of the documentation. One principle runs through it: anywhere an absence could look like a measurement, it is labelled explicitly. Not tracked rather than 0. No anomaly rather than data missing. No claim permitted rather than unknown. Partial list rather than complete list. Blocklist unavailable rather than empty blocklist.
Data age. The mirror is refreshed daily. Every signal states its data date (from the filename, otherwise
mtime). Once the newest data date passes the threshold, every tool response is prefixed with a staleness banner.Contradictory glossary. A substantial share of terms is held simultaneously as a rejected variant and as an approved equivalent (reported as a concrete number at startup). This is a property of the data source, not resolved by the server but surfaced as the class
WIDERSPRUCH— never quietly "corrected".DE/EN duplication. Many terms exist twice, as a DE and as an EN source entry.
term_check_textconsolidates per position: same position plus same target yields one message; several synonyms mapping to the same approved form yieldEINDEUTIG; only genuinely different targets remainMEHRDEUTIG.term_check_textfinds only what is in the glossary. A wrong term not held there as a rejected variant will not be detected. The tool supplements review; it does not replace it.facts_for_urlmatches by substring. Short product abbreviations can hit URLs unrelated to the product. Every match therefore returns the matched abbreviation asmatchedProduct.EXCLUDE_URLSis only a partial list. Further exclusions (locale variants, utility pages, strategic exceptions) live outside the mirror and are unavailable to this server. Only what is in the file is reported.nullforsupportFact/volumeFactmeans "no claim permitted" — not "unknown" and not "look it up elsewhere". The server never fills such fields.AI impressions exist for the pilot pages only (split DE/EN). For any other page the answer is "not tracked" — not 0.
Empty
ranking_anomaliesmeans "no anomaly", not "data missing".Local files only. No network calls, no writes outside the project directory.
The reference customer blocklist is not part of this repository. It is loaded at runtime from a separate file (path via environment variable, pattern in
.mcp.json.example). If the file is absent,facts://guardrailsdoes not quietly return an empty list but states the gap explicitly — the rule "name no customers" remains in force, plus a warning on stderr.The server must never become a second source of truth. Where logic exists in the production system it is mirrored here, not reinvented. Where mirroring is impossible, the gap is named in the output.
Operation
Environment variables
Variable | Purpose |
| Path to the glossary file. If unset: the newest |
| Root of the SEO mirror (contains |
| Optional direct path to |
All source paths come from environment variables — no path is compiled in.
Concrete values for a given installation are not part of this repository;
.mcp.json.example serves as a template with generic paths.
Start
node server.jsOn startup the server reports on stderr, among other things, the age of the mirror and the number of glossary contradictions. The glossary is loaded once at startup; the SEO/GEO files are read per call.
Health check
claude mcp listRun as a privileged user or from another directory, this command systematically returns nothing — and that looks exactly like a broken server. Always run it as the user owning the mirror, and from the project directory.
Tests
node test/driver.js # stdio session across all tools
node test/diagnose.js # diagnostics for term_check_text match logic (read-only)Data sources → tools
Source | feeds |
terminology glossary ( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
No tool was built on a guessed data shape.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseCqualityDmaintenanceAn MCP server that enables persistent memory, structured thinking sessions, and project-based knowledge management for Claude. It includes specialized coding tools for package discovery and reinvention prevention by validating code against existing libraries and APIs.Last updated173
- Alicense-qualityDmaintenanceMCP server enabling symbol-aware semantic search in Claude Code, allowing precise location of functions, types, and implementations via a symbol graph and embeddings.Last updated9MIT
- Alicense-qualityCmaintenanceMCP server providing RAG context and failure capture for Claude Code, enabling semantic search across project knowledge and storing/analyzing failures.Last updated1MIT
- FlicenseAqualityCmaintenanceMCP server for the ONE Framework ERP that enables Claude to explore data models, run queries, and understand business logic through autonomous access.Last updated10
Related MCP Connectors
MCP server giving Claude AI access to 22+ NYC public-record databases for real estate due diligence
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Reppesgaard-Ninja/marketing-stack-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server