campaign-preflight-mcp
This server is a read-only MCP interface to Campaign Preflight, letting you lint outbound campaigns and get readiness reports without modifying anything.
Run the bundled synthetic demo campaign to see a sample readiness report (
preflight_demo)Check a campaign from local files: campaign, leads, optional senders, suppressions, evidence, and config; omitted inputs are reported as unavailable, not clean (
preflight_files)Inspect a live Instantly campaign by ID: reads campaign details, leads, sending accounts, and workspace block list through a read-only allowlist; API key comes from
INSTANTLY_API_KEY, never as an argument (preflight_instantly_campaign)List the full rule catalogue, optionally filtered by category: campaign, contacts, suppression, personalization, copy, schedule, or senders (
list_preflight_rules)Get a detailed explanation of any single rule, including what data it needs and its configurable options (
explain_preflight_rule)Validate a rules configuration file for unknown rule IDs, unknown options, and out-of-range values without running checks (
validate_preflight_config)Control report detail with
max_samplesper finding and choose output formats like structured, terminal, JSON, or markdown where supported
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., "@campaign-preflight-mcpRun a preflight check on my Q3 outbound campaign"
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.
Campaign Preflight
Campaign Preflight is a read-only linter for outbound campaigns. It catches configuration, contact-data, personalization, suppression, schedule, and sender problems before launch.
What it does
Every outbound team has shipped a campaign with a mistake in it. Someone who unsubscribed got emailed anyway. A sequence kept following up after the prospect replied. A merge field never merged and two hundred people got "Hi {{first_name}}."
You find out after it sends.
Campaign Preflight runs 76 deterministic checks over a campaign's configuration, leads, copy, schedule, senders, and suppression exposure, and returns a readiness decision with evidence for every finding. It never writes to your provider and it cannot activate anything.
What it does not do, up front rather than buried:
It does not guarantee deliverability. It checks configuration and data, not inbox placement, and never invents a deliverability score.
It does not give legal advice. Region, domain, and opt-out checks compare a campaign against your own configured policy — not GDPR, CAN-SPAM, or CASL.
It does not verify mailboxes. Address checks are syntax only. No DNS, no SMTP.
It does not replace your provider's safeguards. Keep those on.
Results are a point-in-time snapshot. A campaign that passed at 09:00 can be edited at 09:05.
Fuller detail in docs/limitations.md.
Related MCP server: Newsletter Tools
"We checked and it's fine" ≠ "we couldn't check"
A checker that cannot tell those apart is worse than no checker, because it turns a permissions error into a green light.
Campaign Preflight makes the distinction structural. Every provider read returns
data plus the reason it does or does not exist, and every rule declares the
data it needs. If that data is unavailable, the engine short-circuits the rule
to UNKNOWN before it can run. Rules cannot opt out.
Situation | Result |
Suppression list read, nobody matched |
|
No suppression list supplied |
|
Suppression endpoint returned 403 |
|
Zero leads in the campaign |
|
Lead endpoint unreachable |
|
There are four verdicts, not two: READY, READY_WITH_WARNINGS, NOT_READY,
and INCOMPLETE.
Requirements
Python 3.9 or newer. That is the whole list.
The package has no runtime dependencies — it imports nothing outside the
standard library. httpx is an optional extra needed only for the live
Instantly provider, behind a lazy import.
The 3.9 floor is deliberate, and deliberately lower than you might expect. It is the oldest interpreter the plugin can encounter on a user's machine, and since there are no dependencies, nothing forces it higher. CI runs 3.9 through 3.13 plus a bare-interpreter job that installs nothing at all, on Linux, macOS, and Windows.
That combination is what lets the plugin run with no install step: it uses
whatever python3 is already there.
Install
As a Claude plugin (marketplace)
/plugin marketplace add katekruger/campaign-preflight
/plugin install campaign-preflightThe repository is its own marketplace: .claude-plugin/marketplace.json sits at
the root alongside the plugin manifest.
As a Claude plugin (local checkout)
git clone https://github.com/katekruger/campaign-preflight/plugin marketplace add ./campaign-preflight
/plugin install campaign-preflightAs a CLI
Straight from a checkout, with nothing installed at all:
PYTHONPATH=src python3 -m campaign_preflight.cli demoNot yet published to PyPI — this is the only CLI install path today.
As an MCP server
claude mcp add campaign-preflight -- campaign-preflight-mcpSix read-only tools. Nothing that could activate, edit, import, or send. Setup for Claude Code and Claude Desktop: docs/mcp.md.
Quick start
campaign-preflight demoNo API key. No network. No configuration.
CAMPAIGN PREFLIGHT
Campaign: Enterprise Q3 Outbound
Provider: demo
Readiness: NOT READY
Score: 0/100
Confidence: MEDIUM
BLOCKERS
[campaign.stop_on_reply]
Stop-on-reply is disabled: repliers will keep receiving follow-ups.
Remediation: Enable stop-on-reply on the campaign.
[personalization.prompt_injection]
1 contact(s) have prompt-injection text in their personalization.
Affected: s***********a@caldera.example.com
Remediation: Remove the affected personalization and review the enrichment source it came from.
[suppression.contact_listed]
1 contact(s) appear on the active suppression list.
Affected: m**********s@stonebridge.example.com
Remediation: Remove these contacts from the campaign before activation.
WARNINGS
[contacts.missing_first_name]
2 of 20 contacts (10.0%) are missing a first name.
Affected: i**o@summitforge.example.com, r******s@clearwater.example.com
Remediation: Backfill the missing first names, or use a fallback in your copy.
UNKNOWN
[senders.aggregate_capacity]
Sender capacity is unavailable: 1 of 3 senders report no daily limit.
Affected: r***n@example.com
------------------------------------------------------------------------------
Summary:
8 blockers, 17 failures, 21 warnings, 1 unknown, 32 passed
20 leads and 3 sender(s) checked in 0.0s
Confidence is MEDIUM: 1 check(s) could not run.
Point-in-time snapshot. Campaign state may change after this check ran.Note the last finding. One sender reports no daily limit, so total capacity
cannot be summed. Most tools would add up the senders that do report one and
call it a number. This one says it does not know — and drops confidence from
HIGH to MEDIUM because of it.
That distinction is the whole idea.
Checking your own campaign
Once the plugin is installed, describe it in plain language:
Check this campaign before I send it.
Here's my lead list — anything wrong with it? (paste or upload)
I'm sending a 3-email sequence to 200 people, 80 a day, weekdays 9-5 Eastern. Is that okay?
There are three ways in, and none needs an account:
You have | What happens |
A file (uploaded, or on disk) | Checked directly. |
A pasted list or some copy | Written to a scratch file, checked, then cleaned up. |
Only a description | The campaign file is built from what you say, shown to you, then checked. |
Anything you do not know is left blank rather than guessed — a blank field comes back as "couldn't check", which is the honest answer.
From files, on the command line
campaign-preflight check \
--campaign examples/clean_campaign/campaign.yaml \
--leads examples/clean_campaign/leads.csv \
--suppressions examples/clean_campaign/suppressions.csvThree worked examples ship with the repo, one per verdict:
Example | Verdict | Exit |
|
| |
|
| |
|
|
In CI
campaign-preflight check --campaign campaign.yaml --leads leads.csv --fail-on blockerExit codes carry the verdict, so this drops straight into a pipeline. See docs/ci.md.
What is inside
The repository root is the plugin. There is no second copy of the tree.
.claude-plugin/ plugin manifest and marketplace manifest
skills/ the three skills, one directory each
bin/ launchers the MCP server and CLI run through
src/ the Python package: rules, engine, providers, reporters
tests/ unit, integration, contract
docs/ rules catalogue, configuration, MCP, CI, limitations, architecture
examples/ three worked campaigns, one per verdict
scripts/ generators and the plugin packagerSkills
Skill | Use it for |
| Checking a real campaign you supply — a file, a paste, or a description. |
| Watching the checker run against bundled sample data. |
| Which rules exist, what each one tests, and how to retune or disable them. |
The boundaries are deliberate: each description names its own situation and points at its neighbour, so a near-miss lands somewhere recoverable.
What it checks
76 rules across seven categories. Full catalogue: docs/rules.md.
Category | Rules | Examples |
Campaign | 10 | Stop-on-reply disabled, daily volume above threshold, no sending window, dates that leave no sending days |
Contacts | 15 | Malformed addresses, duplicates (exact and case-folded), role inboxes, placeholder values, control and bidi characters, spreadsheet formula injection |
Suppression | 8 | Contacts and domains on your suppression list, existing customers, internal addresses, competitors, restricted regions — and whether the suppression check could run at all |
Personalization | 13 | Unrendered merge tokens, a greeting addressed to the wrong person, a company that isn't theirs, claims unsupported by their own evidence, stale research, prompt-injection text scraped in from a target's page |
Copy | 13 | Empty subject on the first step, broken links, |
Schedule | 9 | Invalid timezone, weekend sending, zero active days, a window that ends before it starts, DST transitions inside the campaign |
Senders | 8 | Mailboxes below your health threshold, error states, volume exceeding capacity — and honest |
Ask the tool about any of them:
campaign-preflight rules list --category suppression
campaign-preflight rules explain senders.aggregate_capacityWhat it deliberately does not check
There is no spam-word rule. "Free" and "act now" are not evidence of anything,
and shipping that list would train you to ignore the tool. Rules that are
judgement calls — copy length, link count, generation artifacts — are marked
heuristic, labelled as such in every report, and are never blockers by default.
Configuration
Campaign Preflight runs with sensible defaults and no config file. Add one when your thresholds differ, or to switch on the checks that depend on your own domain and region lists.
version: 1
settings:
target_timezone: America/New_York
required_variables: [first_name, company_name]
internal_domains: [ourcompany.example.com]
customer_domains: [bigcustomer.example.com]
allow_weekend_sending: false
rules:
campaign.daily_volume:
warning_above: 100
blocker_above: 250
senders.health_below_threshold:
minimum_score: 80
contacts.missing_job_title:
enabled: falsecampaign-preflight validate-config preflight.yaml
campaign-preflight check --campaign c.yaml --leads l.csv --config preflight.yamlValidation is strict on purpose: an unknown rule id or an unknown option is a hard error, not a warning. A typo that silently disables a safety check is worse than no config at all.
Full reference: docs/configuration.md.
Why read-only matters
Campaign Preflight has no code path that writes. Not "we chose not to" — there is nothing to call.
The Instantly provider routes every request through a transport that checks
(method, path)against an explicit allowlist and raises before the request leaves the process. The check sits below the client and below the provider, so a future code change that adds aPATCHfails loudly instead of quietly editing your campaign.Two guards run at import time: the allowlist cannot contain
PUT,PATCH,DELETE,HEAD, orOPTIONS, andPOSTis permitted for exactly one path (/leads/list, which is Instantly's documented shape for a filtered read).The MCP server refuses to start if any registered tool has a mutating verb in its name or does not declare itself read-only.
tests/contract/test_instantly_transport.pyexercises the full method × path matrix plus every documented mutating endpoint. A failure there is a security incident, not a test failure.
This is what makes it safe to hand an agent a live campaign. It gets the analysis and none of the authority.
What it will never do
Activate, pause, resume, or schedule a campaign
Create, update, move, merge, or delete a lead
Add to or remove from a suppression list
Send, reply to, or forward an email
Modify anything in your sending platform
There is no code path to any of these, and two independent guards — the transport allowlist and the MCP startup assertion — fail closed if one is ever added.
Exit codes
Code | Meaning |
|
|
|
|
|
|
|
|
| Configuration or input error |
| Provider or authentication error |
| Unexpected internal error |
--fail-on none|warning|high|blocker raises the bar at which a verdict becomes
a nonzero exit. It never changes the verdict itself. INCOMPLETE is not
silenced by a severity threshold — a check that could not run is a different
problem from a low-severity finding.
Scoring is published, not hidden
score = 100 - sum(weight[status][severity] for every FAIL and WARN)
readiness:
NOT_READY any BLOCKER FAIL, or any HIGH FAIL
INCOMPLETE else if any critical rule is UNKNOWN
READY_WITH_WARNINGS else if any FAIL or WARN
READY otherwiseFour things follow from that, and each has a test:
A blocker always produces
NOT_READY. The number cannot override it.UNKNOWNdeducts nothing. A provider outage must not look like a bad campaign — it lowers confidence instead.NOT_APPLICABLEaffects nothing.Every deduction is itemized.
--verboseprints the arithmetic so you can check it by hand.
Weights and the critical-rule list are configurable: docs/configuration.md.
Architecture
flowchart LR
CLI[CLI] --> Engine
MCP[MCP server] --> Engine
Engine -->|gather| Provider{Provider}
Provider --> CSV[CSV / files]
Provider --> Instantly[Instantly v2]
Instantly --> Guard[ReadOnlyTransport]
Guard -->|allowlist| API[(Instantly API)]
Provider -->|data + why| Context[Frozen context]
Context --> Rules[76 rules]
Rules --> Score[Scoring]
Score --> Out[Terminal / JSON / Markdown]
style Guard fill:#4a1f1f,stroke:#c04040,color:#fffThe context is a frozen dataclass, so "a rule never mutates its input" is enforced by the type system rather than by review. Provider-specific behaviour lives entirely behind the provider interface.
Full design and threat model: docs/architecture.md.
Privacy
Redacted by default. Mailbox local parts are masked (
m**********s@stonebridge.example.com); domains are kept, because a domain is what makes a suppression finding actionable.Secrets are scrubbed unconditionally.
--no-redactdisables PII masking, never credential masking. A provider that echoes your API key back in an error body cannot get it into a report — there is a test for exactly that.Nothing leaves your machine by default. The optional LLM claim evaluator is off unless you configure it, and
validate-configwarns you when a config turns it on.Report files are written
0600, to a temporary file and then renamed.Samples are bounded. A 100,000-lead campaign cannot emit 100,000 lines.
Performance
Workload | Time |
Demo (20 leads) | 0.02 s |
10,000 leads | 0.28 s |
100,000 leads | 3.0 s, ~300 MB peak |
Rows are streamed, not slurped. Pagination, retries, sender concurrency, and output size are all bounded.
Development
git clone https://github.com/katekruger/campaign-preflight
cd campaign-preflight
uv sync --all-extras
uv run pytestuv run ruff format . # format
uv run ruff check . # lint
uv run mypy # typecheck, strict
claude plugin validate . --strict # manifests
uv run python scripts/generate_rules_doc.py --check # docs/rules.md is current
./scripts/bump-version.sh --check # version fields agree
uv run python scripts/build_plugin.py # dist/campaign-preflight.pluginThe package itself has no runtime dependencies; the dev group exists for the
test suite, the linters, and two libraries used only as test oracles — httpx
for the optional Instantly provider and PyYAML to differentially test the
bundled YAML parser against.
Conventions that look like mistakes until you know why are written down in AGENTS.md.
Roadmap
Additional providers behind the same read-only interface (Smartlead, HubSpot Sequences, Apollo)
Domain reputation and DNS record checks (SPF, DKIM, DMARC alignment)
A GitHub Action wrapping the CLI with PR annotations
Baseline comparison: diff two reports and show what changed since the last run
Per-segment thresholds, so one config can cover several motions
Contributing
Rules are small, pure, and independently testable — a new one is usually a class, a docstring, and a handful of tests. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Security
Report vulnerabilities privately: SECURITY.md. A rule that
returned PASS when the data was missing counts as a security issue.
See also
Every project here shares one idea: a GTM system should refuse to act on data it cannot verify.
deliverability-guard — the runtime counterpart. This checks a campaign before it sends; that one watches reputation while it is sending and pauses before a domain burns.
instantly-mcp — the same risk-tiered safety model applied to writes rather than reads.
segment-mcp — a read-first MCP
server for Twilio Segment. Same "checked and it's fine ≠ we couldn't check"
philosophy: insufficient_data is its own state, never coerced to a pass.
License
MIT. See LICENSE.
Available Tools
6 toolsexplain_preflight_ruleBRead-onlyIdempotent
READ-ONLY. Explain one rule: what it checks, what data it needs, and its configurable options.
| Name | Required | Description | Default |
|---|---|---|---|
| rule_id | Yes | e.g. campaign.daily_volume |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered by structured data. The description's 'READ-ONLY' prefix reinforces but does not add to this. It adds minimal behavioral context beyond confirming the rule-explanation output, and does not contradict the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with zero waste. The 'READ-ONLY' prefix overlaps with annotations but is harmless. Appropriately sized for a simple single-parameter tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity tool (1 parameter, no output schema, no nesting), the description is reasonably complete — it states what the returned explanation covers. It partially compensates for the missing output schema by describing the explanation's contents, though it doesn't address error behavior for an invalid rule_id. Adequate given the tool's simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% — rule_id is documented with an example ('campaign.daily_volume'). The description adds no parameter-level meaning beyond implying that rule_id selects the rule to explain, which the schema already conveys. Baseline 3 is appropriate since the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Explain') and resource ('one rule'), and clarifies what the explanation covers (checks, data needs, configurable options). It implicitly distinguishes from siblings like list_preflight_rules and validate_preflight_config, though it does not name them explicitly. A clear, well-scoped purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is given on when to use this tool versus its siblings. The description does not state that this should be used when detailed information about a single rule is needed as opposed to list_preflight_rules for enumeration or validate_preflight_config for config checking. No exclusions or alternative routing provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_preflight_rulesARead-onlyIdempotent
READ-ONLY. List the preflight rule catalogue, optionally filtered by category: campaign, contacts, suppression, personalization, copy, schedule, or senders.
| Name | Required | Description | Default |
|---|---|---|---|
| category | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, and the description's opening 'READ-ONLY' merely reinforces that. It adds no deeper behavioral detail beyond the filter (which is more parameter-related). No mention of pagination, output format, or rate limits, but for a simple read-only list tool, the annotations carry the transparency burden sufficiently.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that front-loads the read-only nature and the core action ('List'), followed by the only parameter detail. Every word earns its place; there is no redundancy or filler. Ideal conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with one optional enum parameter, no output schema, and no nested objects, the description covers the purpose, the filter, and its optionality. It does not mention return format or relationships to sibling tools, but those are not critical for a simple listing operation. The description is sufficiently complete for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 0% description coverage and only one optional enum parameter. The description explicitly lists all valid category values (campaign, contacts, suppression, personalization, copy, schedule, senders) and clarifies the parameter is optional ('optionally filtered'). This fully compensates for the schema's lack of descriptions, giving the agent complete knowledge of the parameter without opening the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('preflight rule catalogue'), and mentions optional filtering. The purpose is clear and not a tautology. It does not explicitly distinguish from siblings like preflight_demo or preflight_files, but the name and description make the function obvious enough that an agent could differentiate it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives context on the optional 'category' filter and lists valid categories, implying when filtering might be useful. However, it provides no explicit guidance on when to use this tool versus alternatives, nor any when-not-to-use statements. The presence of sibling tools like 'validate_preflight_config' suggests related actions, but no comparison is made.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
preflight_demoARead-onlyIdempotent
READ-ONLY. Run the bundled synthetic demo campaign and return its readiness report. Uses synthetic data shipped with the package: no network calls and no credentials. Useful for showing what a report looks like.
| Name | Required | Description | Default |
|---|---|---|---|
| max_samples | No | Affected records shown per finding. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark readOnlyHint=true, destructiveHint=false, and idempotentHint=true, so the description's 'READ-ONLY' is redundant. However, it adds value by stating no network calls and no credentials, and that it uses synthetic data shipped with the package—behavioral details not present in annotations. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short sentences, each earning its place: the first states the action and safety profile, the second gives key behavioral context, and the third explains practical utility. It is front-loaded with READ-ONLY and has no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple demo tool with one optional parameter and no required inputs, the description fully covers what the agent needs: what it does, that it's safe (read-only, no network/credentials), and when it's useful (showing a report). No output schema exists, but the description doesn't need to detail report structure for a demo. Complete for its purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%—the parameter max_samples is described as 'Affected records shown per finding.' The tool description does not mention the parameter at all, but since the schema fully covers it, the baseline of 3 applies. No additional semantic value added beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool runs a bundled synthetic demo campaign and returns a readiness report. The word 'demo' and 'synthetic' distinguish it from siblings like preflight_files or preflight_instantly_campaign, which likely operate on real data. This is a specific verb+resource with clear differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly notes it's 'useful for showing what a report looks like,' providing a direct use case. It also hints at no network calls and no credentials, implying it's safe for testing. However, it doesn't explicitly state when to prefer this over siblings, so it lacks explicit exclusions or alternative comparisons.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
preflight_filesARead-onlyIdempotent
READ-ONLY. Check a campaign described by local files and return its readiness report. Reads only the paths given here; no directory is scanned or walked. Optional inputs that are omitted are reported as unavailable capabilities, so a run without suppressions_path says the suppression checks did not run rather than implying the list is clean.
| Name | Required | Description | Default |
|---|---|---|---|
| leads_path | Yes | Absolute or relative path to an existing file. | |
| config_path | No | Absolute or relative path to an existing file. | |
| max_samples | No | Affected records shown per finding. | |
| senders_path | No | Absolute or relative path to an existing file. | |
| campaign_path | Yes | Absolute or relative path to an existing file. | |
| evidence_path | No | Absolute or relative path to an existing file. | |
| output_format | No | Shape of the returned report. | |
| suppressions_path | No | Absolute or relative path to an existing file. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly and idempotent annotations, the description adds critical behavioral context: it explicitly states that no directory scanning occurs and that omitted optional inputs are reported as 'unavailable capabilities' rather than clean results. This clarifies how the tool handles missing parameters, which is a significant behavioral nuance beyond the annotations alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences with no filler. The first sentence states purpose, the second clarifies input scope, and the third explains behavior on omission. It is front-loaded with the core action and each sentence earns its place by contributing distinct, necessary information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 8 parameters, no output schema, and no explicit enumeration of the checks performed, the description provides sufficient context: it defines the tool's domain (local files), its safety profile, and handling of missing inputs. The output_format parameter handles return shape, and sibling tools list_preflight_rules/explain_preflight_rule can cover rule details. It is complete enough for an agent to use correctly, though slightly more detail on what 'readiness' entails would elevate it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already provides descriptions for all 8 parameters (100% coverage), so the baseline is 3. The description adds meaning by explaining the collective effect of omitting optional parameters (e.g., suppressions_path yields 'suppression checks did not run'), which is a parameter-level semantic not present in the schema. This justifies a 4 over the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('Check a campaign described by local files and return its readiness report') with a clear resource (local files). It distinguishes itself from siblings by emphasizing 'local files' and the scoping rule 'Reads only the paths given here', which sets it apart from tools like preflight_demo or preflight_instantly_campaign that likely handle other sources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for file-based campaigns and clarifies that only explicit paths are read, but it does not explicitly contrast with sibling tools or state when to choose this over preflight_instantly_campaign or preflight_demo. The guidance is implied rather than directive, so it misses the 'when/when-not' bar for a higher score.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
preflight_instantly_campaignARead-onlyIdempotent
READ-ONLY. Inspect a live Instantly campaign and return its readiness report. Reads the campaign, its leads, its sending accounts, and the workspace block list. Every request is checked against a read-only allowlist before it leaves the process, so no write, activation, or lead-mutation call is reachable. The API key is read from the INSTANTLY_API_KEY environment variable and is never accepted as a tool argument.
| Name | Required | Description | Default |
|---|---|---|---|
| lead_limit | No | ||
| campaign_id | Yes | Instantly campaign UUID. | |
| config_path | No | Absolute or relative path to an existing file. | |
| max_samples | No | Affected records shown per finding. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, but the description adds valuable context: every request is checked against a read-only allowlist, and the API key is sourced from INSTANTLY_API_KEY rather than a tool argument. This discloses operational safeguards and security details beyond the annotations, enhancing the agent's understanding of constraints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise at three sentences, with purpose front-loaded ('READ-ONLY. Inspect...'). However, there is slight redundancy: 'READ-ONLY' is repeated in the allowlist explanation. Despite this, the structure is efficient and each sentence adds relevant information—purpose, scope, and security mechanism.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no output schema, the description should explain what the readiness report contains, but it only says 'readiness report' without detailing its format or contents. It does mention the data sources (leads, accounts, block list) but not the report's structure. Given the absence of output schema, this is a notable gap. The description covers the operational context well (read-only, API key), but the return value remains underspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75% (campaign_id, config_path, max_samples have descriptions; lead_limit has only min/max). The tool description does not elaborate on any parameter semantics. Per the rubric, with high schema coverage, a baseline of 3 is appropriate. The lack of compensation for the undocumented lead_limit is a minor gap, but overall the schema carries most of the parameter information.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's action and target: 'Inspect a live Instantly campaign and return its readiness report.' This is a specific verb-resource combination and explicitly distinguishes it from sibling tools (preflight_demo, preflight_files, list_preflight_rules, etc.) by the 'live Instantly campaign' scope. The mention of reading leads, sending accounts, and block list further clarifies the exact domain.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains the scenario (inspecting a live campaign) and the read-only nature, but does not explicitly compare with alternatives or state when not to use it. It provides clear context for when this tool is appropriate (live campaign inspection) without listing excluded cases or directing to other preflight tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_preflight_configARead-onlyIdempotent
READ-ONLY. Validate a rules configuration file without running any checks. Reports unknown rule ids, unknown options, and out-of-range values.
| Name | Required | Description | Default |
|---|---|---|---|
| config_path | Yes | Absolute or relative path to an existing file. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already carry the safety profile (readOnlyHint=true, idempotentHint=true, destructiveHint=false), and the leading 'READ-ONLY' merely restates readOnlyHint. The added value is the description of what validation reports (unknown rule ids, unknown options, out-of-range values), which is useful but modest. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with no filler. The critical READ-ONLY safety flag is front-loaded, and the second sentence efficiently enumerates what the validation surfaces (unknown rule ids, options, out-of-range values). Slightly redundant with annotations but well-structured and brief.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter, read-only validation tool, this is nearly complete: purpose, scope, safety, and output dimensions are all covered. No output schema exists, but the description names the three classes of findings an agent can expect, which is sufficient for a config-validator of this simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%; the schema already documents config_path as an absolute or relative path to an existing file. The description adds only the contextual hint that the path points to a configuration file, which aligns with but does not materially extend the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Describes a specific verb (validate) applied to a specific resource (rules configuration file). It distinguishes itself from sibling tools like preflight_demo, preflight_files, and preflight_instantly_campaign by explicitly stating it does not run checks, and from list_preflight_rules/explain_preflight_rule by focusing on config validation rather than rule discovery. An agent can pick it apart from siblings without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a clear context: validate a configuration file before executing checks. The phrase 'without running any checks' implicitly marks off the preflight_* siblings that do execute campaigns, giving a when-not signal. It does not name alternatives explicitly or give exclusion conditions, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
v0.1.0- First observed
explain_preflight_rule - First observed
list_preflight_rules - First observed
preflight_demo - First observed
preflight_files - First observed
preflight_instantly_campaign - First observed
validate_preflight_config
TDQS
Scored across 6 tools
Each tool serves a clearly distinct purpose: three preflight runners for different input sources (demo, files, live), two for rule management (list and explain), and one for config validation. There is no overlap in their operations, and the descriptions make it obvious which to select.
Names use a consistent 'preflight_' prefix across all tools, but the pattern is not fully uniform: the first three are verbs implied by the domain (e.g., 'preflight_demo' runs a demo), while the last three explicitly start with verbs ('list_', 'explain_', 'validate_'). This is a minor deviation that agents can easily adapt to.
With six tools, the server is well-scoped and avoids unnecessary bloat. Each tool covers a distinct aspect of the preflight workflow—execution sources, rule catalog, and configuration—so the count feels appropriately sized for the domain.
The surface covers the core lifecycle: running preflights on demo, files, or a live Instantly campaign; inspecting and understanding rules; and validating config files. A minor gap is the lack of a tool to run preflight on a manually specified set of parameters or a generic input, but the provided sources handle common use cases adequately.
Maintenance
Related MCP Connectors
Render markdown into email-safe HTML, lint drafts for deliverability problems, and preview emails.
Hosted MCP server for inbox health, reporting, and warmup operations.
Evidence-readiness MCP server: validate, audit, and score briefs, memos, and evidence packs.
Read-only MVR preflight for trust, permission, evidence gaps, and African market-entry readiness.
Related MCP Servers
- AlicenseBqualityFmaintenanceA Model Context Protocol server that provides read-only access to Mailchimp's Marketing API for comprehensive email marketing data retrieval.38186 npm11MIT
- FlicenseNot gradedqualityDmaintenanceA utility MCP server providing 10 specialized tools for newsletter content preparation and optimization, including subject line generation, HTML-to-text extraction, read time estimation, and email validation. Enables newsletter operators, developers, and content teams to automate pre-send workflows and audit newsletter issues through natural language interactions.-
- AlicenseAqualityAmaintenanceLocal-first production-readiness MCP server for AI-built apps. It runs read-only checks, produces an evidence-based readiness score, and guides fixes before launch.115Apache 2.0
- AlicenseAqualityCmaintenanceA deterministic preflight checker for outbound SMS/iMessage that catches silent filtering, segment blowups, and dropped iMessage features before sending.126 npm1MIT