Graftcode Impact Programme: this project is approved for participation in the Graftcode Impact Programme. A Python service (an article-research-and-drafting pipeline) called directly from a TypeScript dashboard and a Node script through Graftcode, with no REST layer: no FastAPI routes, no Pydantic DTOs, no hand-written fetch client.
Browser (React) ── generated Graft ── WebSocket ─┐
Node test script ─ generated Graft ──────────────┤
▼
Claude / Cursor ── MCP over HTTP ──────► Graftcode Gateway ─► ArticlePipelineService (Python)
:8080 ws · :8081 /mcp plain public methods
There is no HTTP adapter: the browser imports the generated Graft and talks to the Gateway directly. MCP is the Gateway's built-in endpoint, not a separate bridge.
It was built for Wayzyy's content engine. You do not need that pipeline to run this repo: without it the receiver runs in demo mode on bundled sample data.
Implementation video MCPUSE
The repository has one Python service and several ways to use it. The dashboard and Node client use the generated Graft over WebSocket; MCP-compatible clients use the Gateway's built-in HTTP endpoint. They all reach the same public Python methods.
flowchart LR
A[React dashboard] -->|generated Graft<br/>WebSocket :8080| G[Graftcode Gateway]
B[Node.js client] -->|generated Graft<br/>WebSocket :8080| G
C[Claude / Cursor] -->|MCP over HTTP<br/>/mcp :8081| G
G --> S[ArticlePipelineService<br/>plain Python methods]
S --> D[(Topics and drafts)]
S --> P[Optional real pipeline<br/>research, writing, publishing]
The important boundary is the Python service. The UI, scripts, and AI assistants are clients of the same service rather than separate copies of the content logic.
The demo models an editorial workflow from an idea to a reviewable change. Sources and rules inform the draft, while the human review step remains the publishing gate.
flowchart LR
I[Topic queue] --> R[RSS and web research]
I --> K[Keyword research]
R --> O[Outline]
K --> O
B[Existing blog] --> L[Style and rulebook]
L --> O
O --> W[Write draft]
W --> E[SEO and style checks]
E --> H{Human review}
H -->|needs changes| W
H -->|approved| PR[Stage GitHub PR]
H -->|approved| X[Optional publisher]
The bundled receiver runs this flow in demo mode with sample data. Attaching the real article pipeline replaces the sample sources and placeholder writer with the project's live research, LLM, and publishing integrations.
For the expanded diagrams and demo notes, see
docs/architecture.md.
| Method | With the real pipeline attached | Demo mode (default for new users) |
|---|---|---|
getPipelineStatus |
Real provider/queue status | Reports demo mode and sample counts |
listPendingTopics, getTopicSources |
Reads the scanner's review queue | Reads receiver/sample_data/review |
listDrafts, getDraft, saveDraft |
Reads/writes the writer's drafts | Reads/writes receiver/sample_data/drafts |
runStyleCheck |
Heuristic brand check on the draft text | Same |
suggestNextArticle |
Ranks the real queue (keyword tiers + source count) | Ranks the sample queue |
writeArticle |
Runs the full LLM research → outline → write pipeline | Writes a clearly labelled placeholder draft |
runScanner |
Runs the scanner project's main.py --scan |
Reports skipped (demo mode) |
pushToGithub |
Opens a real GitHub PR | Raises an error (needs the pipeline) |
Beyond the original demo, the dashboard includes a visual Pipeline Studio: build a content pipeline from steps (RSS, web research, learn-from-your-blog rules, keyword research, outline, write, SEO check, human review, Reddit ideas, GitHub pull request), run it and watch each step finish, plan content on a calendar by drag and drop, schedule drafts with a GitHub Action, and manage several blogs with their own API keys. It works offline out of the box. See STUDIO.md.
Open http://localhost:5173 after start.sh. It calls the Python receiver straight from the
browser through the generated Graft (WebSocket).
| Tab | What it exercises |
|---|---|
| Dashboard | getPipelineStatus, listPendingTopics, listDrafts, plus the live "Run topic scanner" and "Suggest next article" actions |
| Topic Queue | listPendingTopics, getTopicSources (the sources modal) |
| Draft Studio | getDraft, saveDraft, writeArticle (Auto-Pilot Draft), runStyleCheck, pushToGithub (Stage PR) |
| Style Auditor | Result of runStyleCheck |
| Architecture | Live benchmark of the same method through the Graft and through MCP, the live MCP tool list, and the known limits |
With the real pipeline attached, Auto-Pilot Draft calls LLM APIs, Scan for New Topics calls the search API, and Stage PR can open a real pull request. In demo mode none of these cost anything.
Screenshots are optional, but these four make the project easier to evaluate:
- Pipeline overview: Topic Queue, research, drafting, review, and GitHub staging.
- Draft Studio: a generated draft beside its style-check result.
- Architecture: the Graft/MCP comparison and the available MCP tools.
- GitHub staging: the branch and pull-request URL from a test repository.
Place them in docs/images/ and redact API keys, personal access tokens, private URLs, and
unpublished customer content.
Client settings (optional): VITE_GRAFT_HOST (default ws://localhost:8080/ws) and
VITE_GRAFT_MCP_URL (default http://localhost:8081/mcp).
- Python 3.10+ and Node 18+
- PyYAML for the configuration file:
pip install -r receiver/requirements.txt - The Graftcode Gateway (
gg): https://docs.graftcode.com/quick-start
bash scripts/start.shThis starts the gateway, installs the freshly generated graft package into test-client/
and ui/, runs the Node → Python test, and starts the dashboard at http://localhost:5173.
Graft Vision (the gateway's inspector) is at http://localhost:8081.
bash scripts/stop.sh # stop everything
bash scripts/start.sh --no-ui # skip the dashboardRun the Python tests (they use demo mode and need no network or keys):
cd receiver && pip install pytest && python -m pytestEverything niche-specific lives in a YAML file, so the same receiver, Graft, MCP tools and
dashboard can serve any blog. Copy receiver/config/wayzyy.yaml,
edit it, and point PIPELINE_CONFIG at it before starting:
PIPELINE_CONFIG=my-blog.yaml bash scripts/start.sh| Section | What it controls |
|---|---|
brand |
Name and description (shown in the dashboard), the topic keyword the style check looks for, the fallback research prompt, and niche / offering / offering_plural, which the article pipeline uses in its prompts and closing call to action (set them, or a new niche inherits Wayzyy's) |
categories |
How queue topics are grouped (first matching keyword rule wins, else default) |
suggestion.tiers |
Which topic keywords the "suggest next article" ranking favours, highest priority first |
style |
Banned words and openers, the attribution keyword (for example a stock-photo site), the publish threshold |
data_dir |
Folder with review/ and drafts/ samples used in demo mode |
Keys you leave out fall back to the built-in defaults, and a malformed file stops startup with a
clear Config error message. A second, non-travel example is in
receiver/config/examples/saas/. Try it with no keys:
PIPELINE_CONFIG=receiver/config/examples/saas/config.yaml \
WAYZYY_WRITER_DIR=/nonexistent WAYZYY_SCANNER_DIR=/nonexistent bash scripts/start.shThe editorial and editorial_rules sections also let a project owner control the quality
contract: minimum length, keyword frequency, required sections, source links, image attribution,
human review, tone, required elements, and banned phrases. These rules are injected into the
planner/writer prompts and enforced again by the receiver's style audit.
Pipeline Studio is designed for more than a single blog owner. A ghostwriter, agency, or in-house content team can use one workspace per client and keep that client's voice in the Rulebook, while reusing the same pipeline shape. Keyword research supports four practical modes:
- Manual: paste a client's approved terms, one per line, with no external key.
- TinyFish: discover related wording and questions from pages that rank. This is useful for ideation, but it does not supply dependable search volume or difficulty.
- DataForSEO: retrieve volume, difficulty, and intent when prioritisation needs measurable SEO data.
- Sample: try the complete flow offline before connecting a provider.
The Keyword research settings let the user choose country and language, filter by search intent, set minimum volume or maximum difficulty, and exclude phrases such as a competitor, location, or irrelevant product. Provider intent is used when available; otherwise Studio makes a small, transparent phrase-based inference. The output reports how many candidates survived and adds the top ten to Content planner. The user then chooses the primary keyword before the outline and writing steps run.
For repeat work, save the configured keyword step or Rulebook as a workspace template. Templates can be exported and imported between client workspaces, but they contain no API keys. Keep the keys in Settings & keys. The Rulebook is the shared quality contract for every client: voice, length, headings, source requirements, FAQ, calls to action, and banned phrases are used in prompts and checked again by SEO. The intended daily flow is:
brief → keyword shortlist → primary keyword → topic → research → rulebook → outline → draft → SEO → human review → PR
This makes the project useful as a client-facing ghostwriting tool as well as a Wayzyy-specific content engine: a user can change the inputs and editorial rules without rewriting the receiver or the Graft/MCP integration.
The site publisher remains an adapter: agent/site_publisher.py currently targets Wayzyy's
React/TSX blog shape. To publish to another site, keep the receiver and editorial config but
replace that adapter for the target repository, then set PUBLISHER_MODULE and
PUBLISHER_FUNCTION in .env. The adapter should return the same fields (slug, branch,
commit_hash, files_modified, and message) so the dashboard can show the PR result.
If you cloned this as the graftcode/ folder of the mcp-use repo, the real pipeline is the
parent folder. Attach it with an absolute path (the gateway runs from a different working directory):
WAYZYY_WRITER_DIR="$(cd .. && pwd)" bash scripts/start.shCopy .env.example and export the variables you need, for example WAYZYY_WRITER_DIR,
WAYZYY_SCANNER_DIR and WAYZYY_SITE_DIR. The pipeline reads its own LLM and search keys
from its own .env; this repo never stores secrets.
The Gateway serves every public receiver method as an MCP tool at http://localhost:8081/mcp
(JSON-RPC over HTTP with an Mcp-Session-Id session). Check it from a terminal:
bash scripts/mcp-check.shCursor (.cursor/mcp.json):
{ "mcpServers": { "graft-content-pipeline": { "url": "http://localhost:8081/mcp" } } }Claude Desktop only speaks stdio, so bridge the endpoint with mcp-remote:
{
"mcpServers": {
"graft-content-pipeline": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8081/mcp"]
}
}
}The Pipeline Studio adds studio_* tools; studio_write_article and studio_decide write and approve an article in
one call each (see STUDIO.md).
MCP tool names are the Python method names (suggest_next_article, write_article, ...). Results
come back as JSON text. The earlier hand-written FastMCP bridge is kept in legacy/ as a
"before" comparison only.
- No HTTP layer between the UI and Python:
grep -rn "fetch(" ui/srconly finds the MCP client inui/src/graft.ts; there are no route handlers anywhere in the repo. - MCP is built in:
bash scripts/mcp-check.shlists the Gateway's tools with no extra process running. - Latency: open the dashboard's Architecture tab and press Run 20 calls each. It times the same method through the Graft and through MCP on your machine. The numbers are local round trips only.
receiver/article_receiver/service.py the Python receiver (plain class, public methods)
receiver/config/ default config and a non-travel example
receiver/sample_data/ demo-mode topics and drafts
receiver/tests/ pytest suite
scripts/ start.sh, stop.sh, run_gateway.sh, mcp-check.sh
test-client/ Node script calling the receiver through the graft
ui/ Vite + React dashboard (calls the Gateway directly)
legacy/ the superseded FastMCP bridge (comparison only)
- Typed returns. The receiver defines dataclasses for its results, but with the Graftcode Node SDK at the time of writing, object results reach Node as opaque handles (fields are not readable as plain properties; lists are not arrays). The public methods therefore return JSON strings, and the dataclasses serve as the schema.
- Package names and registry IDs change. The generated package name and the registry ID
can differ per machine and per gateway restart, so
start.shreads them from the gateway log and writesgraft-package.jsonfor the callers. - Draft files are sandboxed. Draft filenames must be plain names. Absolute paths,
.., and symlinks that resolve outside the drafts folder are rejected for every method that reads or writes a draft (regression tests inreceiver/tests/test_security.py). The gateway itself still has no authentication, so this limits what a caller can reach but does not replace authentication. - Quality gates are configurable.
editorial.minimum_words,maximum_primary_keyword_uses,required_sections,require_sources,require_image_attribution, andrequire_human_revieware project-level controls. They produce audit flags; a human still decides whether to publish. Every point the audit deducts comes with a recommendation. The keyword-frequency limit applies only when a draft records its ownprimary_keyword:in its front matter, because the audit cannot know it otherwise. - Security. The gateway only allows
http://localhost:5173by default (GRAFT_CORS), has no authentication (including on/mcp), andpushToGithubcan open PRs when configured. Do not expose it publicly. - Typed returns in the browser too. The same opaque-handle behaviour applies to the browser SDK, so the dashboard also receives JSON strings and parses them.
- Dashboard shows "MCP: offline"? The browser must be allowed to read the
Mcp-Session-Idheader.run_gateway.shwrites a CORS config forhttp://localhost:5173; if you serve the dashboard from another origin, setGRAFT_CORSand restart. - Port already in use? Run
bash scripts/stop.sh, or changeGRAFT_PORT,GRAFT_VISION_PORT. - Python not found by the gateway? Set
PY_HOMEto your Python prefix (for example the python.org framework build) and re-run.
MIT. The Graftcode gateway (gg) and the generated Graft packages are
Graftcode's own software and are governed by their terms; this repository does not redistribute them.