byline
Byline is an MCP server that lets AI tools research, write, and publish blog posts to Ghost or WordPress in a specified author's voice, with image generation, media management, and scheduling.
Manage sites: list, add, remove, and health-check publishing targets.
Manage authors and personas: list platform authors and detailed voice/style/tone personas.
Research: fetch attributable, dated findings from Brave or Tavily with source URLs.
Writing assistance: build persona-specific writing briefs and score drafts for quality, burstiness, AI-tells, HTML validity, and citation provenance.
Images: generate photorealistic images via Gemini or Grok and upload them to the blog's media library.
Publishing and editing: create posts with full metadata, schedule them, update existing posts, and correct metadata.
Media library: list, find, and track usage of local images to avoid reuse.
Video embedding: embed YouTube, Vimeo, or Bunny Stream videos as iframe HTML.
Health checks: probe blogs, image, and research providers for connectivity and credentials.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bylineWrite a post about our migration and publish to work blog as a draft."
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.
Byline
Say what you want to say. Byline writes it in your voice and publishes it where it belongs.
You have the idea. Byline turns it into a finished, credible piece — researched, written as you, illustrated with real photographs — and puts it live. You talk to it in plain English inside the AI tool you already use. There is no dashboard, no editor, no config file to maintain.
Built and open-sourced by IndiaNIC — the team that builds MCP servers and AI agents into other people's workflows for a living. This one we gave away.
Write a post about why our migration took nine months, as me, and publish it
to the company blog as a draft.That is the whole interface.
Today it publishes to Ghost and WordPress. The architecture is channel-agnostic on purpose: adding a destination is one folder and one line, so a newsletter, a local paper's submission inbox, a social channel, or an email to your team are all the same shape of problem. Every one of them has a byline.
Many voices, many destinations. Write as yourself on one blog and as your company on another — each persona carries its own writing style, tone, sentence rhythm, and the things it would never say. You pick both in the same sentence: "as jordan, to the personal blog". See Author personas.
60-second quickstart — nothing to a published post
Where to get each key — click-paths, with the one trap
Add Byline to any AI tool — including ones it cannot auto-detect
Troubleshooting — real failures, real fixes
What you need
Node 20 or newer. Check with
node --version. Anything older prints one clear line telling you so — not a stack trace.A blog you can publish to — Ghost or WordPress, either self-hosted or managed.
An AI tool — Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI, or Codex.
You do not need to know what MCP is, edit any JSON, or write any code.
Related MCP server: Ghost CMS MCP Server
60-second quickstart
1. Point npm at the registry — one line, once, in ~/.npmrc:
echo '@indianic:registry=https://npm.indianic.in/' >> ~/.npmrcThat registry is anonymously readable. There is no login, no npm adduser, and no
auth token — people assume otherwise and go looking for credentials that do not exist.
2. Install and run the setup wizard:
npm install -g @indianic/byline
byline init3. Answer the questions. It finds the AI tools already on your machine, then asks you
to give this blog a short name — myblog, work, whatever you like — followed by
its address and one key. It checks that key against your live blog before accepting it.
Every question can be skipped by pressing Enter on an empty line.
4. Restart your AI tool, then type the sentence init printed for you at the end:
Write a blog post about <your topic> and publish it to myblog as a draft.myblog there is the short name you chose in step 3. Saying draft means nothing
goes live and no subscriber is emailed — start there.
That is the whole thing. Everything below is detail for when you want it.
What is an MCP server?
If you have used Claude or Cursor, you have noticed they cannot touch anything outside the chat. They can write you a blog post, but they cannot put it on your blog.
MCP is the standard that fixes that. An MCP server is a small program that runs on your own machine and hands your AI tool a set of things it is allowed to do. byline hands it fourteen: look up your blogs, check they are reachable, build a writing brief, score a draft, generate and upload images, create and update posts, and — only if you configure a key for it — fetch dated, citable research on a topic.
Two consequences worth knowing:
It runs locally. Your AI tool starts it on your machine. Nothing about byline is a hosted service, and there is no account to create.
Your keys stay on your machine. They are read from a file only you can read, and sent only to the blog and image APIs you configured. See Where your secrets live.
"Registering" byline with an AI tool just means adding a few lines to that tool's
config file so it knows the server exists. byline init does that for you, and
backs up the file first.
Install
The one-line registry config
@indianic/byline is published to a private-namespace registry, so npm needs to be
told where the @indianic scope lives:
echo '@indianic:registry=https://npm.indianic.in/' >> ~/.npmrcNo authentication is required. The registry is readable anonymously. If you find
yourself hunting for a token or an npm login, stop — you do not need one.
Install it
npm install -g @indianic/bylineOr skip the install entirely and let npx fetch it on demand:
npx -y @indianic/byline initBoth give you the byline command.
Add Byline to any AI tool
byline init detects and configures Claude Code, Cursor, Windsurf, Gemini CLI and
Codex automatically. Any other MCP-capable tool — Antigravity, Claude Desktop, Zed,
Continue, JetBrains AI, or something released next month — takes one manual step,
because Byline will not write to a config file it has not verified exists.
Open that tool's MCP settings and add one server. Almost every tool uses this shape:
{
"mcpServers": {
"byline": {
"command": "npx",
"args": ["-y", "@indianic/byline"]
}
}
}A few tools use TOML instead (Codex is one):
[mcp_servers.byline]
command = "npx"
args = ["-y", "@indianic/byline"]Three things worth knowing:
npxkeeps you current. It resolves the latest published version each time. If you would rather pin it, install globally and use the absolute path fromwhich byline— but note that path is tied to your Node version and breaks when you switch withnvm.Restart the tool afterwards. MCP configs are read once, at startup.
Confirm it worked with
byline status, which reports every tool it can see and which scope each registration is in.
To do the same thing from the command line for a tool Byline does know:
byline register --tools claude,cursor # specific tools
byline register --tools all # every tool found on this machine
byline register # just print the command, change nothingInstalling from source
You do not need this to use Byline — it is here for contributors and for anyone who wants to pin an exact build.
git clone https://github.com/indianic/byline.git && cd byline
npm install && npm run build
npm pack # produces indianic-byline-1.0.0.tgz
npm install -g ./indianic-byline-1.0.0.tgzThen register with the absolute path rather than the npx form:
claude mcp add byline -- "$(which byline)"Note that path is tied to your current Node version — if you switch Node versions with
nvm, re-run byline register --tools all.
Staying up to date
Byline checks the registry at most once a day and tells you, after a command finishes, when a newer version exists:
● A newer version is available: 1.0.0 → 1.1.0. Run `byline update` to upgrade.byline update re-installs using whichever package manager put it there — npm, pnpm or
yarn, detected rather than assumed. If you use the npx form, you are always on the
latest and nothing is needed.
Silence it with BYLINE_NO_UPDATE_CHECK=1. It is skipped automatically in CI, and it
never runs while Byline is serving your AI tool.
byline init
One command sets everything up. Here is a real run, start to finish.
Home directory paths and the blog hostname below are substituted for examples — everything else is verbatim output.
It finds the AI tools you already have
┌ byline — first-run setup
│
◆ Register byline with which AI tools? (space to toggle, enter to confirm)
│ ◼ Claude Code
│ ◼ Cursor
└Only tools whose config files actually exist on your machine are offered, and all of them start ticked. It never creates a config for a tool you do not have.
◆ Config scope
│ ● Global — available in every project (recommended)
│ ○ This project only — writes into the current folderIt backs up before it writes
◆ AI tool config
│ Claude Code: updated /Users/you/.claude.json
│ backup: /Users/you/.claude.json.byline-bak
│ Cursor: updated /Users/you/.cursor/mcp.json
│ backup: /Users/you/.cursor/mcp.json.byline-bakYour existing MCP servers and settings are left alone — the file is merged, not replaced.
It asks for your blog
◆ Which kind of blog?
│ ● Ghost
│ ○ WordPress
◇ Short name for this blog, used when you say "publish to …" (Enter nothing to skip)
│ myblog
◆ Your blog address (Enter nothing to skip)
│ https://blog.example.comThe short name is what you will say to your AI tool — "publish it to myblog". Use lowercase letters, digits, and hyphens.
It tells you exactly which key to get
● Admin API key — Ghost Admin → Settings → Integrations → Add custom integration.
Copy the ADMIN API key, not the Content API key — they are not interchangeable.
(looks like: id:secret)
│
◆ Admin API key (Enter nothing to skip)
│ _Your typing is masked. Every prompt can be skipped by pressing Enter on an empty line — you can set up the rest now and come back.
It checks the key against your real blog before accepting it
This is the part worth trusting. Enter a wrong key and you find out immediately, from your blog, in its own words:
◇ Admin API key (Enter nothing to skip)
│ ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
│
◇ Could not connect to https://blog.example.com
■ Ghost rejected these credentials (HTTP 401):
│ Unknown Admin API Key
│
◆ What now?
│ ● Try again (re-enter the credentials)
│ ○ Skip this siteAnd a key that works says so:
◇ Connected to https://blog.example.com — Example Blog (Ghost 6.44.1)A key that has not been proven to work is never written to your config. This check is made against an endpoint that genuinely requires the credential — not one that answers anybody — which is a distinction this project learned the hard way.
It tells you where everything went
◆ blog "myblog"
│ config /Users/you/.byline/config.yaml
│ secret /Users/you/.byline/.env (MYBLOG_ADMIN_API_KEY)
◆ author profile
│ /Users/you/.byline/personas/_template.yaml — copy it to <your-name>.yaml and
│ fill it in to shape how drafts sound
◆ everything written
│ /Users/you/.byline/config.yaml
│ /Users/you/.byline/.env
│ /Users/you/.byline/personas/_template.yaml
◆ what to say in your AI tool
│ Write a blog post about <your topic> and publish it to myblog as a draft.
│
└ Done. Restart your AI tools so they load byline, then paste the line above.Restart your AI tool. It reads its MCP config at startup, so byline will not appear until you do.
Where to get each key
Ghost — the Admin API key
Ghost Admin → Settings → Integrations → Add custom integration, give it any name, then copy the Admin API Key.
The trap, and it catches almost everyone. That screen shows you two keys. The Content API key is a single string and is read-only — it cannot publish, and byline will reject it. The Admin API key is two parts joined by a colon:
6612abcd1234ef567890abcd:9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09If yours has no colon in it, you have copied the wrong one.
The integration is what has permission, not you — so you can revoke it from that same screen at any time without touching your own account.
WordPress — an Application Password
WP Admin → Users → Profile → Application Passwords, enter any name, click Add New Application Password.
This is built into WordPress core since 5.6. You do not need a plugin.
The password is displayed in space-separated groups, like
abcd EFGH ijkl MNOP qrst UVWX. Copy it with the spaces. byline sends it exactly as you paste it; reformatting is a silent way to break a paste that worked.It is shown once. If you lose it, revoke it and make a new one.
You will also be asked for your WordPress username — the login name, not your display name and not your email.
Google Gemini — for images (optional)
Google AI Studio → Get API key → Create API key (https://aistudio.google.com/apikey). The free tier is enough to start.
xAI / Grok — the image fallback (optional)
console.x.ai → API Keys → Create API key. Only used when Gemini fails.
Skip both and everything still works — you just get posts without generated hero images.
Research (optional — you probably do not need this)
Byline works without a research key, and most people should leave it that way. If the
agent you are talking to already has web access, use it: search however you like, paste
what you find as research (at least 200 characters), and news mode is satisfied.
Evergreen posts (mode: "blog") need no research at all, and byline init never requires
a research key to finish.
A research provider buys two things that path cannot give you:
Recency you can check. A finding carries a
publishedAtwhere the provider gives one — it can be absent, which is exactly whatRESEARCH_UNDATEDcatches — and news mode refuses a set where nothing is recent enough, rather than trusting whatever the model recalls.Checkable provenance. Every finding arrives with its URL and, where the provider gives one, its publication date — so
score_draftcan afterwards tell you which of the URLs your draft links were actually in that research.
What Byline checks, and what it simply trusts
This distinction is the whole point of the feature, so it is worth being exact about.
Provider findings are checked. Byline refuses a result with no findings in it at
all, in any mode. In news mode only, it additionally requires at least one finding
carrying a readable publication date inside the window the result declares — allowing six
hours' grace, because providers round timestamps to the hour and a publisher's date is
often the article's rather than its last update. If nothing is dated you are told so; if
everything dated is older than the window, you are told the newest date it found.
Individual findings that are undated or out of window are accepted, not rejected one by
one: an article legitimately cites background alongside its breaking sources. Each is
marked as such next to its own entry on the brief, and counted in a warning.
Blog mode applies no recency check at all.
A research string you paste is trusted, not verified. In news mode it is checked for
one thing — substance, currently 200 characters — and nothing else. Byline did not fetch
it, cannot confirm it is recent, and cannot confirm the text matches any source it
names. The brief says so on its face — the research block is headed ORIGIN: supplied by the caller — TRUSTED, NOT VERIFIED BY BYLINE — so the writer treats every figure in it as
your claim rather than a measured fact. That is not a criticism of the path; it is the
honest description of it.
score_draft's citation_provenance check is advisory. Pass the research findings
to it and it compares the absolute http(s) URLs in your draft's <a href> links against
the findings' URLs, then reports which cited URLs were not in the research and which
research sources went uncited. It never blocks — a writer legitimately links a homepage or
a definition no search returned. Pass no findings and it reports "not evaluated" rather
than passing, because a silent pass would read as "the citations were verified" when
nothing was. And note what it does not do: it checks where a URL came from, never
whether the page at that URL says what your article claims it says.
One article, one origin
Pass research or findings, never both. Supplying both is refused, naming which to
drop. Merging them would make provenance unanswerable — you could not tell which claim
came from where, so nothing could be cross-checked and a later correction could not be
traced to a source.
Brave or Tavily — pick one, there is no fallback
They do not return the same kind of thing:
Returns | Use when | |
Tavily | A synthesized answer plus dated sources | You want orientation as well as sources |
Brave | Ranked results with snippets, no synthesis | You want the raw result list |
Byline never substitutes one for the other. Naming a provider whose key is missing is refused, not redirected, and a failed search is reported rather than retried against the other one. An automatic fallback would silently change what the writer receives — sometimes a summary, sometimes a list.
byline init # offers both; configure either or both, or neitherBRAVE_API_KEY=BSA…
TAVILY_API_KEY=tvly-…
BYLINE_RESEARCH_PROVIDER=tavily # optional: the default when both are configuredWith both configured and no default pinned, registry order decides — and that order was set by measuring which provider dates a minutes-old event more reliably. On one live query inside one two-minute window, Brave's freshest result was ~55 minutes old and Tavily's freshest on-topic dated result was ~4h39m, so Brave is registered first. That is a measurement, not a preference; Tavily's snippets are richer and its synthesis is a capability Brave lacks. The full table is in docs/RESEARCH-NOTES.md.
Where to get each key
Brave —
brave.com/search/api→ subscribe to the free Data for Search plan → API Keys → Add API key (https://api-dashboard.search.brave.com/app/keys).Tavily —
tavily.com→ sign up → API Keys (https://app.tavily.com). The free tier gives 1,000 credits a month and needs no card.
Neither key is a failure to be missing. byline doctor reports an unconfigured research
provider as a note, not an error.
What it looks like in practice
Write about last night's match for personal.The agent decides whether the topic turns on recent events, calls research_topic, passes
the whole result to build_writing_brief as findings, and passes the findings again to
score_draft so citations can be traced. Both tool descriptions tell it to ask you
rather than guess when it is unclear whether you mean the live event or the history —
that is an instruction to the agent, which is as far as an MCP server's reach goes.
Your own photos (optional)
Point Byline at a folder of your own images and it will search them, upload the ones you
pick, and keep a record of which photograph went into which post so the same one is not
published twice. Nothing is generated, nothing is uploaded to a third party, and Byline
never writes inside your library folder — the index and the usage record live under
~/.byline/media/.
Adding a library
byline media add ~/Pictures/blogThis adds the folder to config.yaml, derives a library name from the folder's own name
(override with --name), and scans it immediately — one command, and the library is
searchable right away. byline media also has list, scan, status, release, and
remove; every flag is documented in docs/CLI.md.
Restart your AI tool afterward. loadContext() reads config.yaml once, at MCP
server startup — a library added from a terminal is invisible to an already-running
server until you restart the AI tool talking to it.
Configuring a library by hand
byline media add writes the same media: block shown below, so editing it directly is
still a supported path — for a field the command does not expose (index_path,
reuse_scope), or if you would rather edit YAML than run a command:
media:
default_library: shots # optional; the library used when none is named
reuse_scope: site # "site" (default) or "global" — see below
libraries:
- name: shots # lowercase letters, digits and hyphens
path: ~/Pictures/blog # the folder of your own images
recursive: true # walk sub-folders too; default true
- name: archive
path: /Volumes/Photos/2019
index_path: ~/byline-index # optional; where the index and usage record goField | What it does |
| How you refer to the library. Same rules as a site slug: lowercase letters, digits, hyphens. |
| The folder to index. |
| Walk sub-folders. Defaults to |
| Where the derived index and the usage record are written. Defaults to |
| Which library is used when a tool call does not name one. With exactly one library you never need it. |
|
|
.jpg, .jpeg, .png, .gif, .webp and .avif are indexed as images; .mp4,
.m4v, .mov and .webm as video. Dotfiles are skipped. Anything else is ignored — an
allow-list, so sidecars, RAW files and half-finished exports never fill your search
results.
The three tools
list_media_libraries— what is configured, how many assets each library holds, and when it was last scanned. Passscan: trueto walk the folder and build the index; that is how a new or changed library becomes searchable from inside your AI tool.byline media scandoes the same thing from a terminal, andbyline media addscans the folder it has just added.find_media— search by keyword and get ranked candidates back, each with awhynaming the tokens that matched, so you can judge the match rather than trust a score. Already-used assets are excluded by default.use_media— upload the assets you picked to a site and record them as used. It returns a hosted URL per asset, ready forfeature_imageor an inline<img>.
In practice you never name them:
Find a photo of the harbour in my library and use it as the hero for that post.What "already used" means, exactly
A photograph is used from the moment its bytes reach your blog — not from the moment a
post goes live. use_media records a reservation; publishing a post that carries the
hosted URL turns it into a published record naming the post. Both count as used, because
a reservation means the image is already sitting in your media library on the platform.
use_media refuses an asset the record says has been used, names where it went, and
carries on with the rest of the batch. Pass allow_reuse: true when publishing the same
photograph twice is what you actually want.
Under the default reuse_scope: site, "used" means used on that site — the same photo is
still free for a different blog. Set reuse_scope: global if a photo should be published
once and never again anywhere.
What this release does not do
Stated plainly, because a promise is worse than a missing feature:
No enrichment. Nothing writes captions or keywords, so ranking is based on what your files and folders are named.
beach-sunset-goa.jpginside2024/holiday/searches well;IMG_4821.jpginsideCamera Roll/does not.No video upload. Videos are indexed and searchable so you can see what you have, and
use_mediarefuses to upload one — the upload path handles images only.No way to cancel a reservation from an MCP tool. If
use_mediasucceeds and the post then fails to publish, that asset stays reserved — no tool clears one. From a terminal,byline media release <id>does (seedocs/CLI.md); without terminal access,list_media_librariesstill reports the count and names the ledger file to edit by hand.
The usage record is not recoverable if you delete it, which is why it is kept in a
separate file from the index (<name>.usage.json beside <name>.index.json) — a rescan
rewrites the index and never touches it.
Video (optional)
Byline does not upload video — that was dropped as a goal entirely. Instead, embed_video
turns a YouTube, Vimeo, or Bunny Stream URL into the <iframe> HTML for an article:
Embed https://youtu.be/dQw4w9WgXcQ in the post, captioned "Watch the full talk".It normalises whatever form you paste — a watch?v= link does not work inside an
<iframe>; /embed/ID does — and refuses anything that is not one of the three
supported providers rather than passing an unrecognised URL through as-is:
Provider | Accepted forms |
YouTube |
|
Vimeo |
|
Bunny Stream |
|
Verified by live probe on 2026-08-12: both Ghost and WordPress (for an account holding the
unfiltered_html capability) keep the <iframe> and its <figure>/<figcaption> wrapper
unchanged on ingest — Ghost additionally wraps it in its own kg-embed-card figure. A
plain <video> tag is a separate, worse option: Ghost strips it completely, with
nothing surviving; WordPress keeps it, for the same account.
For a WordPress account WITHOUT unfiltered_html, whether the <iframe> survives is
UNVERIFIED — WordPress's KSES filter is documented to strip <iframe> for such an
account, but no probe has confirmed it, because no such account has ever been available.
Pass site naming the WordPress site and embed_video adds that caveat to its
warnings; it does not change the HTML produced.
Using it
Talk to your AI tool in plain English. It figures out which tool to call.
Write a blog post about why microservices fail for small teams and publish it to myblog as a draft.
Draft an article on AI agents in fintech for myblog, as jordan.
Take the same topic and write a shorter version for the company blog, in the company voice.
Check my blogs are working.
Name the voice and the destination in the same sentence. "as jordan, to the personal blog" picks a persona and a site together. Different personas produce genuinely different articles from the same topic — different structure, different sentence rhythm, different opinions — because each one carries its own writing style, tone, risk tolerance, and the things that person would never say.
Say "draft" and you get a draft. Nothing goes live, and no subscriber is emailed, unless you ask for a published post. Start there.
Ask it to check first. "Check my blogs are working" runs health_check against
every configured site and tells you which ones authenticate.
News articles always need real research. If you ask for a piece about recent events, the brief tool refuses to proceed on the model's training data alone — you have to give it actual research, either as your own notes or from a research provider. Evergreen posts have no such requirement, and neither path needs a key you do not already have. See Research.
Author personas (optional)
A persona is what makes an article sound like a person instead of a content mill. Byline writes in first person as that author, and carries roughly fifteen traits into every draft: writing style, tone, communication style, storytelling approach, sentence structure, focus areas, research methodology, personality traits, bias tendency, how much risk they take in an opinion, cultural context, and their own free-text instructions.
byline init asks for it directly — five questions (name, role, writing style and
tone, years of experience, subject expertise), each skippable. Answering them writes a
real, working persona; skipping goes straight to a template you fill in by hand. Either
way, init prints the exact file path so you always know where to look. Add as many as
you like — copy ~/.byline/personas/_template.yaml to your-name.yaml and fill it in;
the filename must match the slug field inside:
~/.byline/personas/
jordan-reyes.yaml # you, on your own blog
company-editorial.yaml # the house voice, on the company blogThen choose per article: "write this as company-editorial and publish to the company blog." You never edit a config file to switch — you say which one.
The one field worth spending time on is persona_specific_instructions_for_ai. It goes
into the brief verbatim, so it is where a real constraint belongs:
persona_specific_instructions_for_ai: |
Ground every claim in delivery experience. Name the real trade-off, not the
marketing version. Never write a paragraph that could apply to any company.The persona shapes the writing — it does not get announced in it
A persona pasted into a prompt has a signature: every article opens by introducing the same person the same way. "As a CEO with 25 years in enterprise delivery…" Once, that reads as authority. Every week, it reads as a template.
So how much of you reaches the page is drawn fresh per article, like the hook and the structure already were. Across articles: 20% state your credential outright — once, early, and never again — 20% bury it in a subordinate clause of a sentence about something else, and 60% never state your role or your years at all. Those carry authority the way a regular columnist does: through a detail only somebody who has done the work would know, through the scale of the decisions described, through simply assuming the reader knows who is talking.
None of that weakens the byline. Generative engines weight unrepeatable first-hand specificity far above a stated job title — every competing article already has the job title. Your profile still governs tone, judgement, and subject matter on every single piece; what varies is how much of it is said out loud.
Alongside it, each article draws a prose texture — uneven rhythm, conceding the strongest counter-argument before answering it, visibly changing your mind mid-piece, refusing abstraction, writing sentences you could say aloud. Every article also carries a fixed standard aimed at what actually gives machine-written prose away: paragraphs of uniform length, relentlessly parallel lists, and an argument that never once commits to anything — plus a long list of words and constructions to avoid outright.
It will not fake being human by breaking things. Introducing typos, padding for rhythm, or inventing a statistic, a client, a date, or a prior article you never wrote are all forbidden explicitly. An invented specific is the one mistake here you cannot take back after publishing.
Byline doesn't have your keys or your voice until you give them. init collects
credentials interactively and validates each one against the live platform before
accepting it. You can skip every prompt and fill things in later — everything lives in
two files it will tell you the path of, and byline status prints them any time.
How it works
When you ask for a post, your AI tool does the thinking and byline does everything that touches the outside world:
build_writing_briefproduces a brief tailored to the target platform — because what survives publication differs between platforms, and the brief says so up front rather than letting the writer discover it afterwards.Your AI tool writes the draft. That part is not byline's job.
score_draftgrades it — sentence-length variety, AI tells, whether claims carry evidence, whether the HTML will survive this platform's ingest.generate_imageandupload_imagecreate hero and inline images and put them in your blog's own media library. Gemini first, Grok as fallback; when it falls back it tells you it did and why.Images are on by default whenever an image key is configured — you do not have to ask for them. Say nothing about images and you still get a hero and an inline photograph; say "no images" or "just the hero" or hand it your own image instructions and that's what happens instead. Your instruction always wins over the default. If no image key is set up, no image is attempted and none is expected.
Images are photographs, not AI art, and the rules are fixed rather than improvised per article: every prompt names the article's actual subject, in a real setting, with no text anywhere in the frame and no glowing-circuitry abstraction. The hero image always contains people doing the work the article is about, since it is what appears on the post card and every social share.
Four independent axes decide how it is shot, so a blog's images do not read as one template:
axis
varies across
Light and camera
window light, hard midday sun, after dark by screen glow, warm tungsten, cool office fluorescent, blue hour, high-key bright — plus macro close-ups, overhead aerials and low-angle handheld frames
Scene
open-plan floors, glass meeting rooms, private cabins, neighbourhood cafes, building forecourts, lunch tables, stairwells, rooftops, canteens, home workspaces
City
Bengaluru, Singapore, Berlin, São Paulo, Nairobi, Tokyo, Dubai, Toronto, Amsterdam, Mexico City, Warsaw, Ho Chi Minh City
Moment
mid-laugh, mid-argument, deep concentration, relief when something finally works, the tail end of a long day, coffee and thinking
The axes move independently — 400 sample subjects reach 128 of the 144 possible city-and-scene pairs, and effectively every combination is distinct. Naming a real city is also what carries who is in the frame: asking a model for "diverse people" in the abstract produces a stock-library composite, while naming Nairobi or Ho Chi Minh City produces architecture, clothing, light and faces that genuinely belong together.
Roughly one image in twelve is an editorial illustration instead — hand-drawn ink line with flat washes and a limited palette, the kind a newspaper opinion page runs. Both images in a single article always share one city and one medium, so they read as a set rather than two unrelated stock pictures.
Image models sometimes refuse a prompt asking for people. If every provider refuses, byline retries once without people and tells you it did, naming the providers' own reason — you never get a silently peopleless image. A provider that broke rather than refused is reported as the failure it is, not quietly worked around.
create_postpublishes — now, as a draft, or at a time you choose. Then it reads the response back and compares it to what was sent, and reports any field the platform quietly dropped.
That last point is the design rule everywhere in this project: nothing fails silently. Every tool returns a result or an error naming the API and its HTTP status.
Scheduling
Write this up and publish it at 10 AM tomorrow.
Say a time and byline publishes then, on either platform.
The time you say is the time on the blog. Not your laptop's timezone, not the server's, not UTC — the blog's own. "10 AM tomorrow" means 10 AM as that blog's readers experience it, and byline looks the blog's timezone up from the platform itself rather than guessing. Send the identical instruction to two blogs in two countries and they publish at two different instants, on purpose:
blog | its timezone | you say | it publishes at |
a Ghost blog set to | IST |
|
|
a WordPress blog set to UTC | UTC |
|
|
You can still pin an exact instant by writing the offset yourself —
2026-08-04T10:00:00+05:30 or ...Z — and byline takes that at face value without
consulting the blog. Only do that if you actually meant a specific timezone.
Under the hood that is status: "scheduled" plus publish_at, which becomes Ghost's
scheduled / published_at or WordPress's future / date_gmt. update_post
schedules a draft you already have, and unschedules one. A past time with
status: "published" backdates a post instead. The result reports
publish_at_local — the time as the blog's clock reads it — alongside the UTC instant
the platform actually stored.
Three things byline refuses rather than guessing at:
A time under two minutes away. WordPress does not reject a scheduled post whose date is too close. It publishes it immediately, returns
201, and reports no error at all. Measured 2026-08-03: 45 seconds of lead went live, 60 seconds scheduled. After writing, byline re-reads the post and fails loudly if the platform published it anyway — naming the post, its live URL, and the platform's own clock. It does not unpublish it for you; that is your call, not a tool's.A future time with
status: "published". The identical request publishes immediately on Ghost and schedules on WordPress. One input cannot be allowed to mean two opposite things, so byline asks you to say"scheduled"if that is what you meant.A local time that does not exist. On a blog whose timezone observes daylight saving, the clocks skip an hour each spring. Asking for a time inside that hour is refused rather than quietly moved.
If the blog does not report a timezone at all, byline says so and asks for an explicit offset — it never falls back to UTC, because that would publish five and a half hours early for an Indian blog while reporting success.
The platforms really do differ
Measured on 2026-07-29 by publishing the same HTML to both:
Ghost | WordPress (account with | |
Styled | survives | survives |
Styled | unwrapped — text survives, all styling lost | survives, styles intact |
| stripped | survives |
Hand-written heading | overwritten — Ghost generates its own | kept |
JSON-LD into | supported | not supported by core — reported as a warning, not silently dropped |
This is why the brief is platform-aware. On Ghost, a <div> summary card publishes as
unstyled text, so the brief asks for a <table> instead.
A limit worth stating. The WordPress column was measured on a single-site administrator account, which always holds the
unfiltered_htmlcapability. Accounts without it (Author, Contributor, or an ordinary Editor on a multisite network) are expected to have some of that markup filtered — but that path has never been measured, only reasoned from WordPress's documented behaviour. Treat it as unverified. Seedocs/WORDPRESS-NOTES.md.
What each article ships with
Summary block above the first heading; headings phrased as questions a reader would actually type, answered in the first 40–60 words; a closing FAQ; statistics attributed inline with a source and date; Article and FAQPage JSON-LD; and full metadata — excerpt, meta title and description, Open Graph and X card titles, descriptions, and images.
Checking on things
byline status # what is configured, and where every file lives
byline doctor # probe every blog, image and research provider; print a fix per failuredoctor on a healthy install:
┌ byline — doctor (v1.1.0)
│
◆ environment
◇ Node v22.23.1
│
◆ secrets
◇ /Users/you/.byline/.env is owner-only (or absent)
│
◆ blogs
◇ myblog (ghost) — Example Blog (Ghost 6.44.1)
│
◆ image generation
◇ gemini — gemini-2.5-flash-image reachable
│
◆ research
◇ brave — Brave Search reachable, key accepted
│
◆ AI tools
◇ Claude Code /Users/you/.claude.json [global]
◇ Cursor /Users/you/.cursor/mcp.json [global]
■ Gemini CLI not registered
■ Windsurf not registered
■ Codex not registered
│
└ All checks passed.Every failure names its own fix. Full command reference: docs/CLI.md.
Troubleshooting
Every row here is a failure that actually happened during this project's development, with the fix that actually resolved it.
Symptom | Cause | Fix |
Ghost: | You pasted the Content API key. It has no colon in it. | Get the Admin API key — |
Ghost: | Same cause, caught before any network call: the half after the colon must be hex. | As above. |
Ghost: | The Admin API is served on a different host or path than the public site. | Set |
Ghost: post publishes but the body is empty | Ghost expects Lexical JSON unless told the body is HTML. | byline always sends |
Ghost: a styled | Ghost unwraps | Use a |
Ghost: image upload | The upload carried no MIME type. | Handled automatically. If you hit it, the filename has an extension byline does not map. |
Ghost: | Ghost strips it on ingest. | Nothing to fix — do not ask for |
WordPress: | The media upload sent no | Handled automatically; same cause as the Ghost 415 above. |
WordPress: | WordPress core has no field for injecting into | Not a failure — an honest warning. The JSON-LD was not silently dropped; you were told. Use an SEO plugin's own fields if you need it. |
WordPress: the wrong tag gets applied | WordPress's tag search is a substring match — searching | Handled: byline requires an exact, case-insensitive match before reusing a tag, and creates a new one otherwise. |
WordPress: styles stripped even though it worked before | Your account may lack | Publish from an account that holds it. Note: this path is unverified — see above. |
| That provider's key is unset. Byline will not quietly use the other one — they return different shapes. | Set that key, or name the one you did configure, or drop the research provider and paste your own notes as |
| An article has exactly one research origin. | Drop whichever you did not mean. The error names both. |
| Not one provider finding carries a readable date inside the window asked for — often the event is not indexed yet. | Widen the window, try the other provider, or write it as |
| The registration is project-scoped, not global. | Not a problem. |
| npm has not been told where the | Add the one |
The AI tool does not see byline | Its MCP config is read at startup. | Restart the tool. Then |
| Intended. Over a pipe it starts the MCP server; in a terminal it shows help rather than hanging. | Nothing to fix. |
| Your shell's |
|
An unexpected error with no detail | The stack trace is suppressed by default — it is noise for someone who just wants their setup fixed. | Re-run with |
Where your secrets live
Two files, in ~/.byline/:
.env — every secret, and nothing else. Created mode 600: readable and writable
by your user only.
MYBLOG_ADMIN_API_KEY=<your key>config.yaml — everything else. Secrets appear only as ${VARIABLE} references,
never as values:
sites:
myblog:
platform: ghost
url: https://blog.example.com
admin_api_key: ${MYBLOG_ADMIN_API_KEY}
default_site: myblogThat split is deliberate: config.yaml can be shared, committed, or pasted into a bug
report without leaking anything. doctor checks that .env is still owner-only and
tells you if it is not.
Nothing leaves your machine except the requests byline makes to the APIs you
configured — your blog, and your image provider if you set one up. There is no
telemetry, no hosted component, and no account. Your keys are read from .env at
startup and sent only in the Authorization header of requests to your own blog.
To see exactly where everything resolved from, run byline status. To remove it
all, byline reset --yes.
Documentation
Every command, flag, and environment variable | |
Architecture, for contributors and agents | |
How to work on this | |
Verified Ghost behaviour, and why each one matters | |
The same for WordPress, with the unverified parts marked | |
Measured Brave and Tavily behaviour, and the recency table that set the registry order | |
Adding a third platform, written from actually doing it | |
The rules for changing this repository, and what each one cost | |
Release history |
Built by IndiaNIC
Byline is developed and maintained by IndiaNIC and given
away under MIT. Everything in it — the platform probes, the measured behaviour in
docs/*-NOTES.md, the refusals that stop a post publishing at the wrong hour — came out
of work we do for clients, and it is here in full rather than as a demo.
That is also what we do commercially: MCP servers and AI agents that fit the way a team already works, and the integration work that decides whether they survive contact with production rather than stopping at a convincing pilot.
If you have a workflow worth automating, we would like to hear about it.
Talk to us → · www.indianic.com
License
MIT — see LICENSE.
Available Tools
14 toolsadd_siteAdd siteA
Add a publishing target to config/sites.yaml. Secret credential fields are recorded as a reference to a .env variable name; the value itself goes in .env, never in config.yaml.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| slug | Yes | Short name used when publishing, e.g. "indianic". Lowercase letters, digits, and hyphens only. | |
| platform | Yes | One of: ghost, wordpress | |
| credentials | Yes | Keyed by this platform's credential field names. For a secret field pass the NAME of the .env variable holding the value; for a non-secret field pass the value itself. Call list_sites or read the error from a wrong platform to see the field names. | |
| default_author | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description takes on the burden of disclosure. It reveals that secret credential fields are stored as .env variable references rather than literal values, which is important behavioral context. However, it does not mention other traits such as overwrite behavior, validation, or required permissions.
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 compact, with the main purpose stated in the first sentence and a security-relevant caveat in the second. While the caveat repeats schema content, it's relevant and doesn't bloat the description.
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 add operation, the description covers the core purpose and secret handling, but lacks specifics on outcomes, error scenarios, or integration with other site-management tools. The presence of nested objects and no output schema suggests more detail could be helpful.
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 description does not add meaning beyond the schema; the secret-field behavior it mentions is already stated in the credentials parameter description. It also does not clarify the url or default_author parameters, leaving 40% of the schema without additional explanation.
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 function with a specific verb and resource ('Add a publishing target to config/sites.yaml'). This distinguishes it from sibling tools like remove_site and list_sites.
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 (adding a publishing target) but does not explicitly provide when-to-use guidance or contrast with alternatives. The secret-handling caveat offers a directive but not usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
build_writing_briefBuild writing briefA
Build a randomized, persona-specific writing brief covering voice, structure, visual blocks, AEO and GEO. RESEARCH IS MANDATORY IN NEWS MODE, from exactly ONE origin: either your own findings as research (your web access, /last30days, or notes a human pasted — TRUSTED but not verified by Byline), or a research_topic result as findings (checked to exist, and that AT LEAST ONE finding is dated and inside the window; any that are not are marked on the brief). Passing both is refused. Do not summarise a recent topic from your own knowledge — the model cutoff cannot know the last 30 days, and an article built on recalled facts will carry stale or invented figures. If it is unclear whether a topic depends on recent events, ASK THE USER rather than guessing; an evergreen topic should use mode: "blog", which needs no research at all. Returns the seed so a brief can be reproduced, plus researchOrigin and any warnings.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | blog | |
| seed | No | ||
| site | No | Which site this is written for — its platform decides the HTML rules. Defaults to the default site. | |
| topic | Yes | ||
| persona | Yes | ||
| findings | No | A whole research_topic result. Mutually exclusive with `research` — passing both is refused. This is the checkable origin: the findings are checked to exist, and, in news mode, at least one of them to be dated and inside the window it declares. Findings that are undated or outside that window are accepted (an article may cite background too) but marked as such on the brief. | |
| language | No | ||
| research | No | Research findings to ground the article in | |
| word_count | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and it delivers: it discloses refusal behavior, verification criteria, warning marking for out-of-window findings, trust levels for the research origin, and return fields (seed, researchOrigin, warnings). This goes well beyond what the schema provides.
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 a dense single paragraph, but most sentences carry operational rules (refusal, verification, asking the user). It is front-loaded with purpose and avoids filler, though the all-caps emphasis and some repetition of schema text slightly reduce elegance.
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 9-parameter tool with a nested object and no output schema, the description addresses the main ambiguity (research origins) and tells the agent what to return (seed, researchOrigin, warnings). It doesn't describe the brief's output format or side effects, but these are less critical than research handling. It is largely complete for safe invocation.
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 only 33%, so the description must add meaning. It does for the critical parameters research/findings/mode, explaining mutual exclusivity, mandatory news-mode research, and how to choose them, but persona, word_count, language, and seed are left self-evident. Overall it compensates for the low coverage on the high-stakes parameters.
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 opening sentence uses the specific verb 'Build' with a concrete deliverable ('randomized, persona-specific writing brief') and scope ('voice, structure, visual blocks, AEO and GEO'). This clearly differentiates it from siblings like research_topic (research only) and create_post (publishing).
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?
It explicitly states RESEARCH IS MANDATORY IN NEWS MODE, gives exactly one origin, and says passing both is refused. It also directs the agent to ASK THE USER when unclear and to use blog mode for evergreen topics, providing clear when-to-use and when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_postCreate postA
Publish an article. Defaults to status "published" — pass "draft" only when the user asked for a draft, or "scheduled" with publish_at to go live at a set time. The author accepts a persona slug and resolves to that site's author id. HTML must not still contain [[content_image]]. Every article gets a hero (feature_image) and an inline by default when an image provider is configured — refused otherwise; pass images: "hero" | "inline" | "none" to opt out.
| Name | Required | Description | Default |
|---|---|---|---|
| faq | No | Builds FAQPage JSON-LD. Must match the visible FAQ section exactly. | |
| html | Yes | ||
| site | Yes | ||
| tags | No | ||
| title | Yes | ||
| author | No | Byline. Either a persona slug (resolved to that site's author id) or a raw platform-native author id, to attribute the post to someone with no persona file — the id's format is specific to the target site's platform and is not the same across every site. Omit to use the site default_author. Run list_authors against the target site to find its ids. | |
| images | No | Which of the hero image (feature_image) and the in-body <img> are required before publishing. Only enforced when an image provider is configured; pass "none" if this article genuinely has no image. | both |
| schema | No | Inject Article (+FAQPage) JSON-LD into the page head for AEO/GEO | |
| status | No | "published" goes live now, "draft" is not visible, "scheduled" goes live at publish_at (which is then required). | published |
| keywords | No | Feeds Article JSON-LD | |
| og_image | No | Defaults to feature_image | |
| og_title | No | Facebook/LinkedIn card title | |
| meta_title | No | SEO title; defaults to title | |
| publish_at | No | When the post should be published, as a date and a time of day — "2026-08-04T10:00". **It is read in the TARGET BLOG'S OWN timezone, never yours and never the user's.** So "publish at 10am tomorrow" is simply "2026-08-04T10:00": pass the wall-clock time the user said, verbatim. Do NOT convert it to UTC, do NOT convert it to your own timezone, and do NOT ask the user which timezone they mean — the blog decides, and Byline looks its timezone up from the platform. The same string sent to two blogs in two countries is two different instants, on purpose. An explicit offset ("2026-08-04T10:00:00+05:30" or "...Z") is also accepted and is then taken at face value, but only use one if the user actually named a timezone. Required with status "scheduled", where it must be at least 2 minutes in the future. With status "published" it must be in the PAST — that backdates the post; a future time with status "published" is refused, because Ghost would publish it immediately while WordPress would schedule it. The result reports publish_at_local, the time as the blog's own clock reads it — tell the user that one, not the UTC value. | |
| canonical_url | No | ||
| feature_image | No | URL from upload_image | |
| twitter_image | No | Defaults to feature_image | |
| twitter_title | No | X card title | |
| custom_excerpt | No | Shown in listings and feeds | |
| og_description | No | ||
| feature_image_id | No | The native id upload_image returned alongside the url (its `id` field), needed by platforms that reference media by id rather than URL — e.g. WordPress's featured_media. Ghost has no such field and ignores this. | |
| meta_description | No | ||
| feature_image_alt | No | ||
| twitter_description | No | ||
| feature_image_caption | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full behavioral burden. It discloses default status, author resolution, the [[content_image]] placeholder constraint, and image requirement/refusal behavior. While it doesn't cover every edge case, it addresses the most consequential behaviors.
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?
Four sentences, each providing necessary operational detail. It front-loads the core action and then layers critical caveats without redundancy or 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 25-param create tool with no output schema, the description covers the main behavioral complexity (status, images, author) while the schema's rich parameter descriptions handle the rest. It lacks explicit return-value info, but the schema and sibling context partially compensate.
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 60%, and the description adds practical semantics for key parameters: status defaults, author resolution, and image options with default behavior. It compensates for gaps but doesn't fully explain all 25 parameters.
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 'Publish an article' with a specific verb and resource, and clarifies default status (published) and creation semantics. Clearly distinguishes from siblings like update_post and upload_image by framing it as the create/publish action.
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 clear context on when to use 'draft', 'scheduled', or 'published' statuses, and explains author field resolution. Does not explicitly name alternatives like update_post for existing posts, so it lacks explicit when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
generate_imageGenerate imageA
Generate a photograph with Gemini, falling back to Grok. Pass prompt as the SUBJECT ONLY — what is happening and where; the photographic style is applied for you. Writes a PNG under the runs directory and returns its path plus which provider produced it.
| Name | Required | Description | Default |
|---|---|---|---|
| look | No | The camera register from build_writing_brief's IMAGES block, passed verbatim so every image in one article matches. Omitted, one is derived from the prompt. | |
| slot | No | Filename stem, e.g. "hero" or "inline" | image |
| style | No | photoreal_people (default) — a photograph with people doing the work, for the hero/feature image. photoreal_scene — a photograph with no people required. diagram — no photographic styling at all; an escape hatch, and rarely right, since image models render text as gibberish. | photoreal_people |
| aspect | No | 16:9 | |
| prompt | Yes | The SUBJECT only — what is happening and where, specific to this article. Do not include camera, lighting, or style words; those are added for you. | |
| provider | No | Pin a provider; disables fallback |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses key behaviors: provider fallback (Gemini then Grok), file output (writes a PNG under the runs directory), and return value (path plus provider). It doesn't cover failure modes or auth/rate limits, but for this tool the disclosed behavior is substantial and directly actionable.
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 compact—three sentences—and front-loads the primary action and fallback. Every sentence provides essential information (what, how to phrase prompt, output behavior). No redundant wording or 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 tool with 6 parameters, 1 required, and no output schema, the description covers the essential context: purpose, fallback, output location, and return content. It doesn't describe the full parameter matrix, but the schema handles that. The description sufficiently orients the agent without needing to repeat schema details.
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 high (83%), so the schema already explains most parameters. The description adds minimal parameter context—chiefly emphasizing that the prompt should be subject-only and that style is applied automatically. This aligns with existing schema descriptions, so the description adds limited extra meaning 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 a specific action: 'Generate a photograph with Gemini, falling back to Grok.' It identifies the resource (a photograph/PNG) and outcome (writes a PNG, returns path and provider). This distinguishes it from siblings like upload_image, which handle existing images rather than generating new ones.
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 provides explicit guidance on how to use the tool: 'Pass `prompt` as the SUBJECT ONLY' and explains that style is applied automatically. It doesn't explicitly contrast with alternatives (e.g., when to use upload_image instead), but the generation context is clear, making the usage circumstances easy to infer.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_personaGet personaC
Full author profile — voice, style, bias, and instructions.
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It implies a read-only operation but does not state this explicitly, nor does it mention error handling, permission requirements, or what happens when the slug is invalid. Minimal behavioral context is added.
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 extremely short and front-loaded, using a dash to present key attributes. It is efficient and avoids waste, though it is a fragment rather than a complete sentence. Every word contributes meaning.
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 parameter and no output schema or annotations, the description is insufficiently complete. It specifies the general content of the profile but omits the meaning of the input, return format, and any edge-case behavior. An agent would need to guess at slug semantics and result structure.
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 has 0% description coverage, and the tool description does not mention the 'slug' parameter at all. The agent is left without any explanation of what a slug is or how to supply it, so the description fails to compensate for the missing schema 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 'Full author profile — voice, style, bias, and instructions' clearly identifies the tool's output as a comprehensive persona profile, differentiating it from the sibling list_personas tool. Though no explicit verb is present, the resource and scope are specific enough for an agent to infer the retrieval intent.
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 provided on when to use this tool versus alternatives such as list_personas or other author-related tools. The description gives no context about prerequisites, use cases, or situations where a different tool would be more appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
health_checkHealth checkA
Probe every configured blog plus every image and research provider. Returns per-API ok/fail with the real error. Run this first when anything fails.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses the scope (all blogs, image/research providers), granularity (per-API), and result semantics (ok/fail with real error). It does not mention potential side effects or performance implications, but as a health check the behavioral profile is well covered.
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 sentences deliver the purpose, scope, output, and usage directive with no redundancy. The description is front-loaded with the action and immediately provides actionable guidance.
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 parameterless diagnostic tool with no output schema, the description fully covers what it does, what it affects, what it returns, and when to use it. It is complete for its 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?
The tool has zero parameters and schema coverage is trivially 100%, so the description need not add parameter details. The baseline score for 0-parameter tools is 4, and the description appropriately focuses on behavior rather than parameters.
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?
Clear verb 'probe' plus specific resources: every configured blog, image provider, and research provider. The output format (per-API ok/fail with error) is also stated, fully distinguishing this from all sibling tools.
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?
Explicitly instructs 'Run this first when anything fails', giving a clear when-to-use directive. While no alternatives are mentioned, none are needed since this is the designated diagnostic tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_authorsList authorsA
List the author accounts on a site, with their platform ids. Pass an id as create_post's author to byline someone who has no persona file, or copy it into a persona's platform_authors.
| Name | Required | Description | Default |
|---|---|---|---|
| site | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses the output format (platform ids) and adds behavioral context by explaining how the ids can be used in create_post and persona configuration, which goes beyond a simple listing statement.
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 and well-structured: the first sentence states the purpose, and the second provides usage guidance. No unnecessary words or 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?
Given the tool's simplicity (one parameter, no output schema, no annotations), the description covers the core behavior, output, and intended usage. It lacks parameter details but is otherwise complete for a listing tool.
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 provides 0% description coverage for the sole 'site' parameter, and the description only mentions 'on a site' without explaining what a valid site is (e.g., site id or name) or how to obtain it. The description does not compensate for the lack of schema 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 'List the author accounts on a site, with their platform ids,' specifying the verb, resource, scope, and output. It also distinguishes authors from personas, which is helpful given the sibling tool list_personas.
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 provides concrete usage guidance: 'Pass an id as create_post's author to byline someone who has no persona file, or copy it into a persona's platform_authors.' This explains when to use the tool but does not explicitly mention when not to use it or name alternatives like list_personas.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_personasList personasA
List available author personas.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It clearly indicates a read-only listing operation, which is inherently transparent. However, it does not disclose any additional behavioral traits such as ordering, filtering, or whether the list includes inactive or draft personas, which could be relevant for an agent.
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 a single, concise sentence that is front-loaded with the action and object. Every word earns its place; there is no fluff or redundancy. It is optimally sized for the simplicity of the 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?
Given the tool's low complexity (no parameters, no output schema, simple list operation), the description is nearly complete. It could be slightly more informative by noting that the list is of pre-defined/available author personas, but the current level is sufficient for an agent to understand the tool's basic 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?
The input schema has zero parameters, so the baseline is 4. The description adds no parameter information because there are none to describe. This is appropriate and requires no further elaboration.
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 uses a specific verb 'List' and clearly identifies the resource as 'available author personas.' This distinguishes it from sibling tools like list_authors (which likely lists authors) and get_persona (which fetches a specific persona), making the purpose unambiguous.
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?
There is no explicit guidance on when to use this tool versus alternatives. While it is implied that this is for seeing available author personas, the description does not mention when not to use it or how it differs from list_authors or get_persona. The agent receives no directional context beyond the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_sitesList sitesA
List configured publishing targets. Never returns key material.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full responsibility for behavioral disclosure. It explicitly states a key safety guarantee ('Never returns key material') and implies a read-only nature via 'List'. This goes beyond the minimum, though it does not cover authentication or error scenarios.
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 two short sentences, front-loaded with the action and resource. Every word adds value: 'configured' clarifies scope and 'Never returns key material' is a critical guarantee. No redundancy.
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 zero-parameter list tool, this description is fully complete. It states what it returns (configured publishing targets) and an important exclusion (key material). No output schema exists, but the description covers the essential return value sufficiently.
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 tool has zero parameters, so the description does not need to explain any. The baseline score of 4 applies because there is nothing missing regarding parameters.
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 uses a specific verb ('List') and resource ('configured publishing targets'), clearly distinguishing it from sibling tools like add_site and remove_site. The scope is well-defined and immediately understandable.
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 clearly communicates that this tool is for listing configured publishing targets, which is contextually distinct from the add/remove siblings. It does not explicitly exclude any use cases or name alternatives, but the purpose itself is sufficient for an agent to decide when to invoke it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
remove_siteRemove siteA
Remove a publishing target from config/sites.yaml. Leaves .env untouched.
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. The statement 'Leaves .env untouched' provides useful scope, but it does not disclose other behaviors such as reversibility, error handling, or whether the operation is permanent. This is a moderate level of transparency.
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 two concise sentences, front-loaded with the core action and followed by a useful caveat. Every word earns its place, with no redundancy or unnecessary detail.
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?
The tool is simple with one parameter and no output schema, so the description is mostly adequate. It states the action and scope, but it fails to mention return values, error cases, or what happens if the slug doesn't exist. For a clean removal tool, it is acceptable but lacks some contextual completeness.
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 has one required parameter 'slug' with no description, and the tool description does not explain what slug refers to. Given 0% schema description coverage, the description should have clarified that slug is the identifier of the site to remove, but it does not, leaving the agent to infer meaning.
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 uses a specific verb 'Remove' and a clear resource 'publishing target from config/sites.yaml', which precisely defines the action. It also distinguishes itself from sibling tools like add_site and list_sites by focusing on the removal operation.
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 when to use it by specifying the action and the target file (config/sites.yaml), giving clear context. However, it does not explicitly state when not to use it or list alternatives, so it doesn't fully meet the highest standard.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
research_topicResearch a topicA
OPTIONAL: fetch attributable findings on a topic from a single configured research provider (Brave or Tavily). If you already have live web access, use it directly and pass what you find as research to build_writing_brief instead — this tool exists mainly for callers without one. What it adds is provenance: every finding carries its source URL, and its publication date wherever the provider supplies one, so claims drawn from it can be attributed. That provenance is what makes two downstream checks possible: build_writing_brief's news-mode recency guard, and score_draft's citation_provenance check, which cross-checks the URLs a draft links against these findings (advisory, not blocking). A hand-supplied research string gets neither: it is trusted, not verified. Before calling, decide whether the topic actually turns on recent events: "the Ashes result" does, "the history of the Ashes" does not. If that is genuinely unclear, ASK THE USER in chat rather than guessing. Byline never substitutes one provider for another — pick one explicitly with provider, or rely on BYLINE_RESEARCH_PROVIDER or whichever single provider is configured; an unconfigured named provider is refused, never swapped for the other. Findings come back in the provider's own order, with no re-sorting or relevance filtering by Byline — pass the WHOLE result as build_writing_brief's findings argument. Do NOT summarize it into research: that throws away the URLs and dates that make each finding checkable, and turns a verifiable origin into an unverifiable one.
| Name | Required | Description | Default |
|---|---|---|---|
| topic | Yes | What to search for, in the words a person would use | |
| window | No | How far back to reach. Use "day" for something that happened in the last few hours. | week |
| provider | No | Pin a provider. Omit to use BYLINE_RESEARCH_PROVIDER, or the only configured one. Never falls back to the other. Brave returns ranked snippets with no synthesis; Tavily also returns a synthesis (`answer`) plus its own sources. | |
| max_results | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full transparency burden and does so comprehensively. It discloses provider refusal (never swaps), absence of re-sorting or relevance filtering, provenance formatting, the advisory (non-blocking) nature of downstream checks, and the distinction between verified findings and untrusted hand-supplied strings. This goes far beyond the schema.
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?
Though long, every sentence earns its place. The description is logically structured: purpose, alternatives, provenance value, downstream use, recency decision, provider policy, output handling, and explicit do-not-summarize warning. It front-loads the core action and builds context without 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?
This is a complex tool with no output schema and no annotations. The description fills every gap: what it returns (findings with URLs and dates), order, provider behavior, configuration, downstream integration, and failure modes (refused unconfigured provider). An agent has enough context to invoke it correctly and interpret results.
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 75%, so the baseline is 3. The description adds significant value by detailing provider-specific behavior ('Brave returns ranked snippets with no synthesis; Tavily also returns a synthesis (`answer`) plus its own sources') and reinforcing window semantics ('Use "day" for something that happened in the last few hours'). max_results lacks schema description but its schema bounds (1-20, default 10) provide adequate meaning; the description does not add further detail there, so not a 5.
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 opens with a specific verb and resource: 'fetch attributable findings on a topic from a single configured research provider.' It clearly distinguishes itself from sibling tools like build_writing_brief and score_draft by emphasizing provenance and downstream checks, making its unique role unmistakable.
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 explicit guidance on when to use this tool versus alternatives: 'If you already have live web access, use it directly and pass what you find as `research` to build_writing_brief instead.' It also instructs when to ask the user if recency is unclear, and explicitly warns against summarizing findings into `research`, naming the alternative approach clearly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
score_draftScore draftA
Mechanically score a draft for human-voice quality: burstiness, AI-tell phrasing, paragraph uniformity, evidence density, first-hand experience, target-platform HTML validity, and — when findings is passed — whether every cited URL actually came from the research. Verdict blocked means fix before publishing. No external API is called.
| Name | Required | Description | Default |
|---|---|---|---|
| html | Yes | ||
| site | No | Which site this is written for — its platform decides the HTML rules. Defaults to the default site. | |
| findings | No | The `findings` array from the research_topic result this draft was written from. Supplying it enables the citation_provenance check, which verifies every cited URL actually came from the research. Omit it and that check reports "not evaluated" rather than passing. | |
| feature_image | No | The feature image, which lives outside the HTML and is otherwise invisible to this tool. Omit it before the image exists; the check reports "not evaluated" rather than failing. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden of behavioral disclosure. It clearly states the tool is mechanical, makes no external API calls, and explains the conditional nature of the citation provenance and feature-image checks. It does not describe the full output structure, but 'Verdict blocked' hints at the verdict format.
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. It front-loads the core purpose and enumerates checks efficiently, then adds conditional behavior in a compact em-dash clause. Every sentence earns its place.
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?
The tool has no output schema and multiple nested optional parameters, but the description covers the key behavioral aspects: what is scored, when verdicts matter, and what happens when optional inputs are omitted. It lacks explicit thresholds and return-format details, but the guidance is sufficient for an agent to select and invoke the tool 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?
Schema description coverage is 75%, so the schema already documents most parameters. The description adds meaningful context for the two optional parameters: findings enables the citation_provenance check, and feature_image is invisible to the tool unless supplied. This goes beyond the schema's field-level descriptions and helps the agent decide whether to pass them.
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 opens with a specific verb+resource ('Mechanically score a draft') and enumerates the exact scoring dimensions (burstiness, AI-tell phrasing, paragraph uniformity, etc.), making the tool's purpose unmistakable and clearly distinguishing it from sibling tools like create_post or health_check.
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 context: it is meant for evaluating a draft before publishing, as shown by 'Verdict blocked means fix before publishing.' It also clarifies optional behavior with findings and feature_image. However, it does not explicitly state when not to use this tool or name alternative tools for related tasks.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_postUpdate postA
Edit an existing post in place. Only the fields you pass are changed. Also the way to schedule an existing draft (status "scheduled" plus publish_at), to unschedule one (status "draft"), or to correct a post's date (publish_at in the past).
| Name | Required | Description | Default |
|---|---|---|---|
| html | No | ||
| site | Yes | ||
| tags | No | ||
| title | No | ||
| status | No | ||
| post_id | Yes | ||
| og_image | No | ||
| og_title | No | ||
| meta_title | No | ||
| publish_at | No | a date and a time of day — "2026-08-04T10:00". **It is read in the TARGET BLOG'S OWN timezone, never yours and never the user's.** So "publish at 10am tomorrow" is simply "2026-08-04T10:00": pass the wall-clock time the user said, verbatim. Do NOT convert it to UTC, do NOT convert it to your own timezone, and do NOT ask the user which timezone they mean — the blog decides, and Byline looks its timezone up from the platform. The same string sent to two blogs in two countries is two different instants, on purpose. An explicit offset ("2026-08-04T10:00:00+05:30" or "...Z") is also accepted and is then taken at face value, but only use one if the user actually named a timezone. Required with status "scheduled", where it must be at least 2 minutes in the future. With status "published" it must be in the PAST — that backdates the post; a future time with status "published" is refused, because Ghost would publish it immediately while WordPress would schedule it. The result reports publish_at_local, the time as the blog's own clock reads it — tell the user that one, not the UTC value. Note that Ghost refuses to move an already-published post back to "scheduled" (it answers "Your post is already published"); set it to "draft" first. | |
| canonical_url | No | ||
| feature_image | No | ||
| twitter_image | No | ||
| twitter_title | No | ||
| custom_excerpt | No | ||
| og_description | No | ||
| feature_image_id | No | The native id upload_image returned alongside the url, needed by platforms that reference media by id rather than URL — e.g. WordPress's featured_media. Ghost has no such field and ignores this. | |
| meta_description | No | ||
| feature_image_alt | No | ||
| codeinjection_head | No | ||
| twitter_description | No | ||
| feature_image_caption | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses the key behavioral trait that only passed fields are changed, which is valuable for a partial-update tool. It also mentions the scheduling/unscheduling/backdating behaviors. However, with no annotations, it omits details about permissions, error conditions, or whether changes are irreversible, leaving the agent to infer mutation safety.
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, purposeful sentences. The first states the core action, the second explains the partial-update behavior, and the third lists additional use cases. Every sentence earns its place with no redundancy or filler.
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?
The description gives enough context to understand primary use and the scheduling nuances. But with 22 parameters, no output schema, and no annotations, it leaves out return values, error handling, and permission requirements. It is adequate for basic usage but not fully complete for an agent operating autonomously.
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 description adds semantics for status and publish_at by explaining their combined usage for scheduling/unscheduling/backdating, and it clarifies that any subset of fields can be updated. Schema coverage is only 9%, so the description should compensate for the other 20 parameters; it does not, though most property names (e.g., title, tags, html) are self-explanatory.
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 opens with 'Edit an existing post in place,' which clearly states the verb (edit), resource (existing post), and scope (in place). It further distinguishes itself from the sibling create_post by emphasizing edits to existing posts and by enumerating specific use cases like scheduling/unscheduling drafts and backdating.
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 lists when to use the tool: to edit a post, schedule an existing draft, unschedule one, or correct a date. It implicitly signals this is not for creating new posts, but it does not explicitly name create_post as an alternative or state what not to do. Clear context exists, but no formal exclusions are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_imageUpload imageA
Upload a local image to a site's media store and return the hosted URL.
| Name | Required | Description | Default |
|---|---|---|---|
| alt | No | ||
| path | Yes | Local path returned by generate_image | |
| site | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It names the action and return value (hosted URL) but does not disclose side effects, error behavior, or prerequisites. The transparency is adequate but minimal.
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 a single, clear sentence with no redundant or extraneous information. It is appropriately sized for the tool's simplicity.
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 tool with three parameters and no output schema, the description conveys the core functionality and return value. It could mention prerequisites like 'site must be existing' but is otherwise sufficiently complete.
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 only 33% (only 'path' is described). The description does not add meaning for 'site' or 'alt', leaving their roles ambiguous.
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 action (upload), the object (local image), the destination (site's media store), and the result (hosted URL). This distinguishes it from sibling tools like generate_image which creates an image.
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: when you have a local image to host, upload it to a site. However, it does not explicitly mention alternatives, exclusions, or prerequisites such as the site needing to exist.
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. Dates show when Glama detected each change.
14 tool updates
v1.6.1- First observed
add_site - First observed
build_writing_brief - First observed
create_post - First observed
generate_image - First observed
get_persona - First observed
health_check - First observed
list_authors - First observed
list_personas - First observed
list_sites - First observed
remove_site - First observed
research_topic - First observed
score_draft - First observed
update_post - First observed
upload_image
TDQS
Each tool targets a distinct resource and action. Even the list_* tools (sites, authors, personas) are clearly separated by what they enumerate, and the descriptions clarify the differences. No two tools could be easily confused.
The vast majority of tools follow a consistent verb_noun pattern (list_sites, add_site, create_post, update_post). The only deviation is health_check, which is a common compound noun and not confusing, but it breaks the otherwise uniform verb-first style.
14 tools is within the ideal 3-15 range. Each tool serves a clear purpose in the blogging workflow, from configuration and research to image generation and publishing. No tool feels redundant or unnecessary.
The set covers site management, persona lookup, research, brief building, scoring, image handling, and post creation/updating, but lacks any way to read or delete posts. This is a notable gap for a publishing server, as an agent cannot list existing posts or retrieve a post to update it intelligently.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Publish to self-hosted WordPress from AI agents: markdown, images, SEO, and Notion sync.
Create, manage, publish, and analyze Inblog content through AI agents.
SEO, competitor and AI-search data, plus blog management — draft, schedule and publish posts.
Draft SEO blog articles from topic to ready-to-review post.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to manage WordPress sites through natural conversation, supporting post creation, content updates, site queries, and draft-to-publish workflows via the WordPress REST API.9MIT
- AlicenseNot gradedqualityDmaintenanceEnables comprehensive automation and management of Ghost CMS blogs through AI assistants, supporting full CRUD operations for posts, pages, members, media uploads, and bulk content management with enterprise-grade security and performance features.382MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with WordPress blog systems for automated content management and publishing via the WordPress REST API.321MIT

Frase MCP Serverofficial
AlicenseNot gradedqualityBmaintenanceEnables AI agents to research, create, optimize, publish, and track content using the Frase content operating system, integrating directly with WordPress, Sanity, Webflow, Wix, and FraseCMS.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/indianic/byline'
If you have feedback or need assistance with the MCP directory API, please join our Discord server