Writing Tools MCP Server
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., "@Writing Tools MCP ServerCheck for spelling mistakes and readability."
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.
Writing Tools MCP Server
This is a Model Context Protocol (MCP) server designed to provide various text analysis tools, assisting users in improving their writing. It is optimized for Claude Desktop with one-click installation via MCP bundles, and also works with other MCP-compatible tools like Cursor and Windsurf.
MCP servers act as a secure bridge or interface, enabling AI models and language assistants to interact with local applications, tools, or data on a user's machine. This server leverages that protocol to offer its specialized writing-specific analysis capabilities to connected AI clients.
Features
This server provides the following text analysis tools:
list_tools: List all available tools in this server.character_count: Return the number of characters in the input text.word_count: Return the number of words in the input text.spellcheck: Return a list of misspelled words in the input text.readability_score: Return readability scores (Flesch, Kincaid, Fog) for the text, section, or paragraph level. Successful responses include afindingsarray.reading_time: Return the estimated reading time for the text, section, or paragraph level.keyword_density: Calculate the density of a given keyword in the text. Returns{"keyword", "density", "findings"}.keyword_frequency: Count how often each keyword appears in the text (optionally removing stopwords). Returns{"frequencies", "findings"}.top_keywords: Identify the most frequently used keywords in the text. Returns{"keywords", "findings"}.keyword_context: Extract sentences or phrases where a specific keyword appears. Returns{"keyword", "sentences", "findings"}.passive_voice_detection: Detect passive voice constructions in the text.perplexity_analysis: Analyze text for perplexity and burstiness to detect AI-generated content using GPT-2. Successful responses include afindingsarray.stylometric_analysis: Analyze stylometric features (sentence length, length-robust lexical diversity and vocabulary rarity, POS ratios and bigrams, six readability grade-level formulas, syntactic complexity, punctuation idiosyncrasies, hedge/booster rate, per-function-word/Burrows' Delta profile, character n-gram profile) for AI detection, against a built-in or custom baseline. Successful responses include afindingsarray.stylometric_delta: Profile a draft and its revision against the same baseline and report what the revision moved: per-statistic z-score deltas, animproved/regressed/unchangedverdict per dimension, and afindingsarray for the revised text.analyze_sections: Run a selected subset of the analysis tools on every markdown section of a document, plus a whole-document rollup. Each section carries its key, heading level, rendered text, an impact-orderedfindingsarray located atsection:<key>, and per-tool results.
Related MCP server: hypernym-mcp-server
Install
# Run directly from GitHub (no install needed)
uvx --from git+https://github.com/wdm0006/writing-tools-mcp writing-tools-mcp
# Or install from source
git clone https://github.com/wdm0006/writing-tools-mcp
cd writing-tools-mcp
uv sync
uv run run_server.pyThe spaCy en_core_web_sm model is not published to PyPI (Explosion distributes
model wheels through GitHub releases),
so it is not listed as a package dependency. It is downloaded automatically on
first use; to pre-install it - for example to keep test runs hermetic - run:
uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whlMCP Client Configuration
{
"mcpServers": {
"writingtools": {
"command": "uvx",
"args": ["--from", "git+https://github.com/wdm0006/writing-tools-mcp", "writing-tools-mcp"]
}
}
}Server Configuration
The server reads an optional .mcp-config.yaml from its working directory. Unknown keys and
wrongly typed values are ignored with a warning on stderr, and every missing key falls back to
the default below.
perplexity:
model_name: "gpt2" # Hugging Face model used for perplexity analysis
max_length: 512 # Token window per chunk
overlap: 50 # Token overlap between chunks
device: "cpu" # "cpu" pins the model to CPU
language: "en" # Only "en" is supported
thresholds:
ppl_max: 25.0 # Perplexity at or below this counts as an AI signal
burstiness_min: 2.5 # Burstiness below this counts as an AI signal
stylometry:
default_baseline: "brown_corpus" # Used when a stylometric_analysis call omits baseline
custom_baselines_dir: "server/data/baselines/custom_baselines" # Relative paths resolve against the server's working directory
thresholds:
warning_z: 2.0 # |z| for a warning
error_z: 3.0 # |z| for an error
ai_confidence_threshold: 0.7 # Confidence needed to flag AI authorship
model:
keep_warm_seconds: 0 # Seconds to keep spaCy/GPT-2 resident between calls; 0 unloads after every call
logging:
level: "INFO" # CRITICAL, ERROR, WARNING, INFO, or DEBUG
format: "%(asctime)s - %(levelname)s - %(message)s" # Standard `logging` format stringSet logging.level: "DEBUG" when reporting a problem. Logs are always written to stderr — stdout
carries the MCP JSON-RPC stream — and an unrecognized level falls back to INFO with a warning
rather than stopping the server.
stylometry.default_baseline selects the baseline stylometric_analysis measures against when a call
omits the baseline argument, and stylometry.custom_baselines_dir is where custom baselines are
saved and loaded from - relative paths resolve against the server's working directory, the same place
.mcp-config.yaml is read from. Every stylometric_analysis response reports the baseline actually
measured against in its baseline_used field. A config that still carries the removed
stylometry.features key logs an unknown-key warning on stderr and starts normally with the rest of
its settings applied. perplexity.language remains accepted but unread; language is chosen per call
through the perplexity_analysis argument.
Keep-warm models (model.keep_warm_seconds)
By default (0) every model-backed tool call unloads the spaCy pipeline and the GPT-2 weights the
moment it finishes: memory stays flat, and each call pays the model load. Setting
keep_warm_seconds to a positive number turns that end-of-call cleanup into TTL-based eviction —
for that many seconds after a model-backed call, the models stay resident and the next call skips
the load entirely; the first call to finish after the window lapses runs the normal eviction.
Eviction is checked on tool calls, not on a timer, so an idle server holds the memory until its
next call. Explicit cleanup (the server's cleanup_models path) still unloads immediately
regardless of this setting.
The trade is memory for latency: with the default gpt2 model, keeping both models resident costs
roughly 100 MB of RSS (~55 MB for the spaCy pipeline and ~40 MB for the GPT-2 weights, measured on
Linux; larger models configured via perplexity.model_name cost proportionally more). The default
0 keeps the lowest possible footprint and is the historical behavior — a load and an unload on
every model-backed call.
Detector calibration
The detection thresholds above are measured against the committed benchmark corpus:
docs/calibration.md publishes confusion matrices for the shipped flags and
precision/recall/FPR at target true-positive rates for each detector's single measurement. On that
sample (110 human / 110 machine documents), the shipped stylometry flag detects about 9% of
machine text at a 1% false-positive rate and the shipped perplexity flag fires on none of it —
higher recall is available at higher false-positive cost, per the published tables. The file is
regenerated with uv run benchmarks/calibrate_detection.py from the benchmark scores; CI fails
when it drifts, so a threshold change must re-derive the published numbers.
Custom Baselines
stylometric_analysis compares a text's features against a baseline (brown_corpus by default)
and flags whatever is an outlier relative to it. Brown Corpus is 1961 published news and fiction;
it answers "does this read like typical published prose," which is often not the question you
actually want answered. A more useful question for judging your own drafts is "does this read like
my own pre-existing writing" - answered by building a baseline from a corpus of your own text.
uv run scripts/build_baseline.py my_own_voice path/to/txt/files/Each *.txt file in the directory is treated as one document (strip front matter, markdown, and
code fences first - the script analyzes exactly the text it's given). The baseline is saved under
stylometry.custom_baselines_dir from your .mcp-config.yaml (the shipped config points it at
server/data/baselines/custom_baselines, relative to the server's working directory; without a
configured directory, the custom_baselines/ folder inside the server package is used) and is
immediately usable:
stylometric_analysis(text, baseline="my_own_voice")By default the builder (server.stylometry.build_baseline_from_texts) only computes mean/std for
a curated, length-robust feature set: avg_sentence_len, sentence_len_std, fog/kincaid/smog/
coleman_liau/ari/dale_chall (six readability grade-level formulas), mtld/mattr/mtld_lemma
(length-robust lexical diversity, on surface forms and lemmas respectively), mean_word_frequency
(vocabulary rarity, via wordfreq - distinct from diversity: how common the words used are, not
how many distinct words there are), word_len_std, lexical_density, five punctuation-idiosyncrasy
ratios (semicolon_ratio, em_dash_ratio, ellipsis_ratio, exclamation_ratio,
parenthetical_rate), hedge_rate/booster_rate (epistemic-marker word categories),
mean_dependency_distance/subordinate_clause_ratio (syntactic complexity read off the dependency
parse), the ADP/DET POS ratios, a curated 10-bigram POS-sequence profile (see below), a
Burrows'-Delta-style per-function-word frequency profile (see below), and a character n-gram
orthographic profile (see below). Type-token ratio and the hapax legomena rate are deliberately left
out: both fall monotonically as a document gets longer, for any author, so comparing them across a
corpus of mixed document lengths mostly measures length rather than style - mtld/mattr/mtld_lemma
exist specifically as length-robust replacements for them (McCarthy & Jarvis 2010; Covington & McFall
2010). fourgram_repetition_rate and zipf_slope are computed but not in the default set: unlike
ttr/hapax, we haven't verified whether they vary with length, so they're opt-in only. Pass
--all-features to include every feature extract_features computes, length-confounded or not.
Burrows' Delta. Rather than one aggregate function_word_ratio, the builder also tracks each of
StylemetricAnalyzer's ~100 function words individually (mean/std per word across the corpus).
stylometric_analysis z-scores each word against its own baseline entry, then reduces all of them
to one number - burrows_delta, the mean absolute z-score across every word scored - the classic
Burrows' Delta statistic (Burrows 2002), built for exactly this kind of small, single-author corpus.
A large burrows_delta (above the usual warning z-threshold) raises a distinct_function_word_profile
flag. Pass function_words=[] to build_baseline_from_texts (or a custom word list) to change or
skip this dimension.
POS bigrams. Published authorship-attribution work reports POS-tag bigrams/trigrams
discriminating authors substantially better than single-tag POS ratios alone. The builder tracks a
small, curated 10-bigram subset by default (DEFAULT_ROBUST_POS_BIGRAMS - noun- and
verb-phrase-initiation patterns like DET_NOUN, VERB_ADP), scored the same way as pos_ratios
under a posbi_ prefix, rather than all ~289 possible tag combinations - most bigrams are too sparse
per document (a handful of occurrences in an 800-word post) to average reliably. Pass pos_bigrams=
to change the tracked set, or [] to skip this dimension.
Character n-gram profile. A PAN/CLEF-style orthographic fingerprint: character 4-gram relative
frequencies, normalized per document. Individual n-grams are too sparse to z-score the way pos_ratios
or function words are (most 4-grams occur 0-2 times in a typical post), so this is compared as a
whole profile instead - calculate_char_ngram_similarity computes the cosine similarity between a
draft's profile and the baseline's aggregate profile (kept to the top char_ngram_top_k n-grams by
corpus-wide frequency, default 300, to bound the baseline's file size). stylometric_analysis surfaces
this as a top-level char_ngram_similarity (not part of z_scores or flags - there's no calibrated
threshold for it yet). Note this is sensitive to vocabulary/topic, not just style: a post using very
different subject-matter vocabulary from the baseline corpus will score a low similarity for that
reason alone, not necessarily because of authorship. Pass char_ngram_top_k=0 to skip this dimension.
server/data/baselines/custom_baselines/mcginniscommawill_pre2020.json ships as a worked example: 102
pre-2020 posts from mcginniscommawill.com, built with this script.
Domain Baselines
Three shipped baselines cover genres that don't read like 1961 news and fiction, so
stylometric_analysis can measure against prose closer to what you're writing. Load them by name
exactly like the Brown Corpus baseline — stylometric_analysis(text, baseline="essays") — or make
one the default in .mcp-config.yaml:
stylometry:
default_baseline: "technical_docs"Each baseline carries 27 statistics (vs Brown Corpus's 9), built with the default length-robust
feature set. That set supports 9 of the 12 AI indicators (uniform_sentences,
unusual_sentence_length, pos_anomalies, unusual_reading_level, low_mtld,
distinct_function_word_profile via Burrows' Delta, unusual_vocabulary_rarity,
unusual_hedge_rate, unusual_booster_rate) - five more than Brown Corpus's statistics support
(low_mtld, distinct_function_word_profile, unusual_vocabulary_rarity, unusual_hedge_rate,
unusual_booster_rate are unreachable against its 9-statistic set). The remaining three
(low_ttr, low_hapax, function_word_anomaly) need length-confounded statistics the robust
builder deliberately omits; they fire only against Brown Corpus, and only there.
Baseline | Register | Documents | Sources (all US public domain) |
| reflective essay prose — first-person argumentative/expository writing | 144 | Montaigne, Essays (Cotton trans., 1877 ed., PG #3600); Emerson, Essays, First Series (1841, PG #2944); Emerson, Essays, Second Series (1844, PG #2945); Chesterton, Orthodoxy (1908, PG #130) |
| instructional how-to and explanatory documentation | 834 | Milton & Wohlers, A Course in Wood Turning (1919, PG #15460); Noyes, Handwork in Wood (1910, PG #20846); Anderson, Electricity for the Farm (1915, PG #27257); The Boy Mechanic, Vol. 1 (1913, PG #12655) |
| expository scientific writing | 52 | Darwin, On the Origin of Species (1859, PG #1228); Faraday, Experimental Researches in Electricity, Vol. 1 (1831–1852, PG #14474); Einstein, Relativity (Lawson trans., 1920, PG #30155) |
License. Every source work was published in the US before 1929 and is in the public domain; Project Gutenberg's license terms govern the electronic transcriptions, not the underlying texts. The shipped baselines contain only aggregate feature statistics (means and standard deviations) — no corpus text is redistributed. The corpora themselves are never committed; they're re-assembled from the pinned sources (see below).
Provenance and reproducibility. Corpus assembly is scripted and deterministic:
scripts/fetch_domain_corpora.py downloads each source from Project Gutenberg (or a mirror),
verifies every download's SHA-256 against the pin recorded in its WORKS table, strips Gutenberg
boilerplate and illustration captions, splits each work into documents at work-specific heading
patterns (indexes and transcriber notes are skipped), and writes numbered pg<id>_nnnn.txt files.
Running it twice produces byte-identical corpora, and rebuilding a baseline from the same corpora
produces a byte-identical JSON (function-word statistics are serialized in a canonical order). To
rebuild:
uv run python scripts/fetch_domain_corpora.py --corpora-root data/corpora
uv run scripts/build_baseline.py essays data/corpora/essays --description "..."Per-baseline source lists, heading/stop rules, and SHA-256 pins live in the WORKS table at the
top of scripts/fetch_domain_corpora.py. The baselines ship in the wheel
(server/data/baselines/*.json) and are covered by integration tests
(tests/test_domain_baselines.py) for loadability, z-score generation, default-baseline
configuration, and wheel bundling.
Building the Bundle
To create a .mcpb bundle for distribution:
make build-mcpbThis creates writing-tools-mcp.mcpb which can be installed in Claude Desktop.
Usage Examples
You can configure any MCP client (like Claude.ai, Windsurf, or Cursor) to connect to it. Here are some example prompts you could give to an AI assistant connected to this MCP server:
General Analysis:
"List the available writing tools." (Calls
list_tools)"Analyze the text below for readability using the standard scores." (Provide text, calls
readability_score)"Check this document for spelling mistakes." (Provide text, calls
spellcheck)"How long would it take someone to read this blog post?" (Provide text, calls
reading_time)
Keyword Analysis:
"What are the top 5 keywords in the following abstract?" (Provide text, calls
top_keywordswithtop_n=5)"Calculate the keyword density for 'artificial intelligence' in this paper." (Provide text, calls
keyword_densitywithkeyword="artificial intelligence")"Show me all sentences containing the term 'MCP'." (Provide text, calls
keyword_contextwithkeyword="MCP")"Search the web for pages based on the top 5 keyworkds in this text, and compare those pages to mine" (Provide text, calls
top_keywordswithtop_n=5, then passes that to a different web search tool if available)
Style and Structure:
"Identify any sentences using passive voice in my draft." (Provide text, calls
passive_voice_detection)"What's the word count for this paragraph?" (Provide text, calls
word_count)"Get the readability scores for each section of this document." (Provide markdown text, calls
readability_scorewithlevel="section")"Break this document into sections and tell me which section needs the most work." (Provide markdown text, calls
analyze_sections)
AI Detection:
"Analyze this text for signs of AI generation using perplexity analysis." (Provide text, calls
perplexity_analysis)"Check if this essay was written by AI using stylometric analysis." (Provide text, calls
stylometric_analysis)"Compare the writing style of this text against human writing baselines." (Provide text, calls
stylometric_analysis)"Is this text too uniform in sentence structure to be human-written?" (Provide text, calls both AI detection tools)
Revision Prompts
Three MCP prompts support an analyze → revise → verify loop:
guided_revision: Render an impact-ordered revision brief for a document. Pass thefindingsarray from any analysis tool as the optionalfindingsargument (JSON string); every finding'srule,location,message, andfix_hintis listed, highest-impact first. Omit it and the brief tells you which tools to run first.writing_checklist: A pre-flight drafting checklist (structure, sentence variety, hedging and boosters, readability, keywords, voice) to apply while writing.verify_revision: Render astylometric_deltaresponse as a revision verdict. Pass the tool's full response as thedeltaargument (JSON string); the verdict groups every statistic intoimproved/regressed/unchangedwith the z-score movement behind each, lists the revised text's findings, and closes with a verification protocol (fix the regressions first, re-run, stop when they clear or plateau). Malformed input degrades into a note instead of an error.
Seven analysis tools (readability_score, perplexity_analysis, stylometric_analysis, keyword_density, keyword_frequency, top_keywords, keyword_context) attach a findings array to successful responses — located, actionable observations with rule, location, message, and fix_hint fields, where fix hints coach the fix rather than restate the flaw. Stylometric findings are honestly scoped to the chosen baseline: indicators the baseline cannot measure produce no finding. Error responses are unchanged, and passive_voice_detection still returns a plain list of sentences.
Preference-Scoring Evaluation
Whether "revisions move prose toward the baseline" is strong enough to power a writing-quality
score was measured directly, not assumed: docs/preference-foundation.md
scores 13 public-domain edit pairs (1818→1831 Frankenstein, Origin of Species editions, the Alice
manuscript fair copy, Dorian Gray 1890→1891) with the stylometric_delta machinery. The delta
direction agreed with the edited variant at coin-flip rates (pair level 41.7%, 95% CI [15%, 72%];
verdict level 47.6%, 95% CI [41%, 55%]), so the go/no-go call is no-go on a production
preference score — the evaluation is deterministic and reproducible, and the report names the
evidence that would reverse the call.
Reference Writing Loop
examples/writing_loop.py is a runnable reference agent that wires the loop end-to-end against the live stdio server: a weak draft goes to stylometric_analysis, its findings render into a guided_revision brief, a revision is produced, and stylometric_delta verifies the revision actually moved the statistics toward the baseline — with verify_revision rendering the verdict prompt. It exits 0 when verification is clear, 1 when the revision regressed statistics the loop should have caught, and 2 when the loop itself failed (server unreachable, tools missing, error envelopes).
uv run python examples/writing_loop.py # draft -> revise -> verify, exit 0
uv run python examples/writing_loop.py --demo-failure # revision regresses the draft; exit 1A committed transcript of both runs lives in examples/writing_loop.transcript.md: the happy path clears with 14 improved / 0 regressed statistics against the brown_corpus baseline, and the failure-mode run shows the loop refusing to sign off on a revision that moved nine statistics further from the baseline.
Tool Reference
Below is a detailed reference for each tool provided by the server.
list_tools
Description: List all available tools in this server.
Parameters: None
Returns:
list[str]- A list of tool names.
character_count
Description: Return the number of characters in the input text.
Parameters:
text(str): The input text.
Returns:
int- The total character count.
word_count
Description: Return the number of words in the input text.
Parameters:
text(str): The input text.
Returns:
int- The total word count (based on whitespace splitting).
spellcheck
Description: Return a list of misspelled words in the input text.
Parameters:
text(str): The input text.
Returns:
list[str]- A list of words identified as potentially misspelled.
readability_score
Description: Return readability scores using Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog index.
Parameters:
text(str): The text to analyze.level(str, optional): Granularity of analysis. Options:"full"(default): Score the entire text."section": Score the full text and each markdown section (identified by#headings) separately."paragraph": Score the full text and each paragraph (separated by blank lines) separately.
Returns:
dict- A dictionary containing the scores. Structure depends on thelevelparameter. For"full", it returns{"flesch": float, "kincaid": float, "fog": float}. For other levels, it returns nested dictionaries. ReturnsNonefor scores if the text segment is too short.
reading_time
Description: Return the estimated reading time for the input text (based on
textstat). Markdown markup is stripped before estimating, so syntax characters and link/image target URLs do not count toward the time.Parameters:
text(str): The text to analyze.level(str, optional): Granularity of analysis. Options:"full"(default): Calculate for the entire text."section": Calculate for the full text and each markdown section."paragraph": Calculate for the full text and each paragraph.
Returns:
dict- A dictionary containing the estimated reading time in minutes. Structure depends on thelevelparameter.
keyword_density
Description: Calculate the density of a given keyword in the text (case-insensitive, lemmatized). Multi-word keywords are matched as complete, contiguous phrases.
Parameters:
text(str): The text to analyze.keyword(str): The keyword or phrase to search for.
Returns:
dict-{"keyword": str, "density": float, "findings": list}— the density percentage ( (keyword count / total words) * 100 ) plus actionable findings.
keyword_frequency
Description: Count how often each keyword (token) appears in the text.
Parameters:
text(str): The text to analyze.remove_stopwords(bool, optional, default=True): Whether to exclude common English stopwords (e.g., 'the', 'a', 'is').
Returns:
dict-{"frequencies": {keyword: count, ...}, "findings": list}— the frequency map (counts underfrequencies) plus actionable findings.
top_keywords
Description: Identify the most frequently used keywords in the text.
Parameters:
text(str): The text to analyze.top_n(int, optional, default=10): The number of top keywords to return.remove_stopwords(bool, optional, default=True): Whether to exclude common English stopwords.
Returns:
dict-{"keywords": [[keyword, count], ...], "findings": list}— keyword/count pairs sorted by frequency in descending order, plus actionable findings.
keyword_context
Description: Extract sentences where a specific keyword (case-insensitive, lemmatized) appears. Multi-word keywords are matched as complete, contiguous phrases.
Parameters:
text(str): The text to search within.keyword(str): The keyword or phrase to find.
Returns:
dict-{"keyword": str, "sentences": list[str], "findings": list}— the matching sentences plus actionable findings.
passive_voice_detection
Description: Detect sentences containing passive voice constructions (based on a simplified pattern matching using spaCy).
Parameters:
text(str): The text to analyze.
Returns:
list[str]- A list of sentences identified as potentially containing passive voice.
perplexity_analysis
Description: Analyze text for perplexity and burstiness to detect AI-generated content using GPT-2. Computes document-level and sentence-level perplexity along with burstiness (variance of perplexity across sentences). Low perplexity combined with low burstiness is a statistical signal used by AI detectors.
Parameters:
text(str): The text to analyze.language(str, optional, default="en"): Language code (only "en" supported currently).
Returns:
dict- Analysis results including:doc_ppl(float | null): Document-level perplexity score;nullwhen no sentence could be scoreddoc_burstiness(float | null): Burstiness score (standard deviation of sentence perplexities);nullwhen fewer than two sentences were scored, since the standard deviation is undefined theresentences(list): Sentence-level perplexity scoresconfig(dict): Model configuration and thresholdsflags(dict): AI detection flags with confidence and explanations
stylometric_analysis
Description: Analyze text for stylometric features and detect AI-generated content. Computes sentence length distribution, lexical diversity (TTR/Hapax, plus the length-robust
mtld/mattr/mtld_lemma) and vocabulary rarity (mean_word_frequency, viawordfreq), POS ratios and a curated POS-bigram profile, six readability grade-level formulas (Fog, Kincaid, SMOG, Coleman-Liau, ARI, Dale-Chall), syntactic complexity from the dependency parse (mean_dependency_distance,subordinate_clause_ratio), punctuation idiosyncrasies (semicolon/em-dash/ellipsis/exclamation/parenthetical rate), hedge/booster epistemic-marker rates, n-gram repetition and Zipf-slope, a per-function-word frequency profile reduced to a Burrows' Delta score, and a character n-gram orthographic profile compared via cosine similarity. Flags outliers relative to a baseline (built-inbrown_corpus, or a custom baseline built from your own writing) using z-score analysis.Parameters:
text(str): The text to analyze.baseline(str, optional): Baseline corpus name for comparison. When omitted, the configuredstylometry.default_baseline(or"brown_corpus") is used; the response'sbaseline_usedfield names the baseline actually measured against. See Custom Baselines to build your own.language(str, optional, default="en"): Language code (only "en" supported currently).
Returns:
dict- Stylometric analysis including:features(dict): Extracted stylometric features (sentence length, TTR/hapax/mtld/mattr/mtld_lemma,mean_word_frequency,word_len_std,lexical_density, POS ratios,pos_bigram_ratios,fog/kincaid/smog/coleman_liau/ari/dale_chall,mean_dependency_distance,subordinate_clause_ratio, punctuation-idiosyncrasy ratios,hedge_rate/booster_rate,fourgram_repetition_rate,zipf_slope,function_word_freqs, etc. -char_ngram_profileis computed internally forchar_ngram_similaritybelow but omitted here, as a several-hundred-entry intermediate)z_scores(dict): Z-scores of features against the baseline, including per-wordfw_<word>scores and the aggregateburrows_delta, and per-bigramposbi_<tag>_<tag>scoresflags(dict): AI detection flags with confidence levels and explanationssentence_analysis(list): Per-sentence analysis with z-scoreschar_ngram_similarity(float | null): Cosine similarity between this text's character n-gram profile and the baseline's (see Custom Baselines);nullwhen the baseline has no character n-gram profile (e.g.brown_corpus)baseline_used(str): Name of the baseline the analysis was measured against - the explicitbaselineargument, the configuredstylometry.default_baseline, or"brown_corpus"config(dict): Baseline information and analysis thresholds
stylometric_delta
Description: Verify a revision: profile a draft (
text_a) and its revision (text_b) against one baseline and report what the revision actually moved. Both texts run through the samestylometric_analysispipeline against the same baseline, and every statistic the baseline can measure in both texts is reported as a movement. A statisticimprovedwhen the revision moved its z-score closer to zero (into the baseline's range),regressedwhen the movement pushed it further out, andunchangedotherwise - the sign never matters, only the distance.Parameters:
text_a(str): The draft text (the "before").text_b(str): The revised text (the "after").baseline(str, optional): Baseline corpus name both texts are measured against. When omitted, the configuredstylometry.default_baseline(or"brown_corpus") is used; the response'sbaseline_usedfield names the baseline actually used. See Custom Baselines to build your own.
Returns:
dict- Delta analysis including:baseline_used(str): The baseline both texts were measured against.deltas(list): One{statistic, z_a, z_b, delta, direction}entry per shared statistic (sorted by name), wheredelta = z_b - z_aanddirectionis"increased","decreased", or"none"- the raw movement, independent of whether it helped.verdict(list): One{statistic, verdict}entry per delta, running parallel todeltas, with verdict"improved","regressed", or"unchanged".text_b_analysis(dict): The revised text's fullstylometric_analysisresponse, so the verdict carries its own evidence.findings(list): Actionable, located observations about the revised text (see Revision Prompts); pass the whole response to theverify_revisionprompt to render it as a revision verdict.
analyze_sections
Description: Run a selected subset of the analysis tools on every markdown section of a document, plus a whole-document rollup. Sections come from the same parser as the section/paragraph analysis levels: heading-keyed and hierarchy-aware (a subsection's body folds into its parent), with pre-first-heading content preserved as its own
_leading_contentsection (heading level 0). Each section entry carrieskey,heading_level, the rendered sectiontext, an impact-orderedfindingsarray (located atsection:<key>), and per-toolresults— each exactly the response that tool returns for the section text at its default settings. Therollupcarries each selected tool's whole-document response (findings included), identical to the standalone tool's output, so per-section views can be checked against whole-document analysis. An empty document yields no sections with the rollup still computed; a document with no headings yields the single_leading_contentsection.Parameters:
text(str): The markdown document to analyze.tools(list[str], optional): The analysis tools to run per section. Choose from:readability,word_count,character_count,reading_time,spellcheck,passive_voice,perplexity,stylometry. Defaults to everything except the GPT-2 tools (perplexityandstylometrystay opt-in since they are the expensive tier). Unknown names yield{"error": ...}naming the valid menu.baseline(str, optional): Baseline name passed through to the per-section and rollup stylometry runs (ignored unlessstylometryis selected). See Custom Baselines.
Returns:
dict- Section batch analysis including:sections(list): One entry per section, in document order:key(str),heading_level(int, 0 for leading content),text(str),findings(list, impact-ordered — same shape the standalone tools attach), andresults(dict, per-tool raw responses)rollup(dict): Each selected tool's whole-document response — identical to the standalone tool's output,findingsincludedtools_used(list): The validated selectionsection_count(int): Number of sections analyzed
Detector Benchmark
benchmarks/ is an offline evaluation surface, separate from the MCP server. It scores
the AI-detection code against committed labeled corpora and writes a report, so the
shipped thresholds can be argued about with numbers instead of intuition. It changes no
tool, default, or threshold.
uv run benchmarks/run_benchmark.pySee benchmarks/README.md for what the corpora are, what the
report contains, and how far the results reproduce. Corpus text is third-party and is
licensed separately from this repository's code.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
This is MIT licensed
This server cannot be deployed
Maintenance
Related MCP Connectors
SEO MCP server for keyword research, SERP analysis, audits, and Search Console workflows.
MCP Server for Slima - AI Writing IDE for Novel Authors with AI Beta Reader.
An MCP server that integrates with Discord to provide AI-powered features.
MCP server for Midjourney AI image generation and editing
Related MCP Servers
- FlicenseAqualityCmaintenanceMCP server for Originality.ai that provides tools for AI detection, plagiarism checking, fact verification, readability analysis, grammar/spelling, and SEO optimization.52-
- FlicenseNot gradedqualityDmaintenanceMCP server providing semantic text analysis and compression tools via Hypernym AI's API.6-
- FlicenseNot gradedqualityDmaintenanceA professional MCP server for analyzing content against the official Microsoft Writing Style Guide, enabling style, grammar, terminology, and accessibility checks via AI tools like VS Code and GitHub Copilot.1-

winston-ai-mcpofficial
AlicenseAqualityAmaintenanceMCP server for Winston AI's detection tools, enabling AI content detection, plagiarism checking, image analysis, and text comparison.4162 npm9MIT