Skip to content
Conduit

mcp server

blogger-mcp

MCP server for Blogger: draft, edit, publish and schedule posts, manage pages and moderate comments from Claude or any MCP client. Includes an Agent Skill.

tutul/blogger-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "blogger-mcp": {
      "command": "uvx",
      "args": [
        "blogger-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 21, 2026

blogger-mcp

An MCP server for Blogger, built on the Blogger API v3.

It gives an MCP client — Claude Code, Claude Desktop, or anything else that speaks the protocol — a set of tools for working with a Blogger blog: list and search posts, create drafts, edit them, publish or schedule them, take them back down, manage static pages, and moderate comments. The intended use is turning documents you already have into blog posts without leaving the tool you wrote them in.

It is Python, it runs locally over stdio, and it authorizes as you through a normal Google OAuth consent flow. There is no hosted component and nothing leaves your machine except calls to Google's API.

Status and maintenance

Provided as-is, with no promise of support. It is published because the code works and because several of the Blogger API's undocumented behaviours (listed below) took real effort to find and are worth writing down.

Concretely, that means:

  • No commitment to respond to issues or pull requests, and no release schedule.
  • No commitment to keep up with changes to the Blogger API. It is a stable, long-lived API, but if Google changes something this may quietly break.
  • Bug reports are welcome and may sit unanswered. Forking is entirely fine — the MIT license is there for exactly that.

If you need something dependable for production, read the code first and plan to maintain your own copy.

What it can and cannot do

The Blogger API v3 is the ceiling here, and it has real gaps. Worth knowing before you build a workflow on top of this.

Supported

  • Posts: list / get / search / create / update / publish / schedule / revert to draft / delete
  • Pages (static pages like About): list / get / create / update / delete
  • Comments: list and moderate (approve, mark spam, remove content, delete)
  • Blogs: read only — list the account's blogs, look one up by id or URL
  • Pageviews: coarse 7-day / 30-day / all-time counts

Not supported — by the API itself, not by this server

  • No image upload. There is no media endpoint. Post bodies are HTML, and every <img> must already point at a publicly reachable absolute URL. Host images elsewhere first (Drive, GitHub, an image host) and paste in the URL.
  • No creating, renaming or deleting a blog. The blogs resource is read-only.
  • No theme, template, layout, widget or blog-settings editing.
  • No label management. Labels are just strings on a post; renaming a label across a blog means patching every post that carries it.
  • No posting or replying to comments. Moderation only.
  • No Markdown. Content is raw HTML (this server can also wrap plain text).
  • No per-post metadata of your own. The customMetaData field exists in the API schema and is documented as "The JSON meta-data for the Post", but Blogger discards writes to it — the value is already null in the insert response. There is no place to record which local file produced a post.

Undocumented API behaviour worth knowing

These were found by running against a live blog. None of them appear in the reference documentation, and three fail silently:

  • fetchBody=False on a write destroys the post body. It reads as a response-shaping option, and is one on posts.get. On posts.insert and posts.patch, Blogger applies it to the write and blanks the content. No error is returned. This server never sends it on a write.
  • customMetaData is a no-op. Writes are discarded silently; the value comes back null in the insert response itself.
  • Whether a delete goes to the trash depends on the post's state, not just on useTrash. Deleting a published or scheduled post with useTrash=true moves it to SOFT_TRASHED, where posts.revert restores it as a draft with its content. Deleting a draft removes it for good even with useTrash=true. Deleting something already in the trash removes it for good — so delete is not idempotent, and retrying one is not safe. Pages behave the same way.
  • blogs.listByUser rejects view=ADMIN with HTTP 400, even though the API's own discovery document lists ADMIN as a valid value for that parameter. Every other resource accepts it.
  • posts.search ignores whose blog it is. It is the one read that takes no view parameter, so it will search any public blog: a mistyped blog_id that happens to belong to someone else returns their posts, with no error. Every other read, given view=ADMIN, rejects that with HTTP 403. This server checks admin access before searching for that reason.
  • Enum values are UPPERCASE. orderBy=PUBLISHED, sortOption=DESCENDING, comment status=PENDING. The HTML reference page documents several of them in lowercase, which the client library rejects outright.
  • Post creation is rate limited fairly aggressively. A burst of a dozen creates returns HTTP 429 rateLimitExceeded; it recovers within a couple of minutes. There is no automatic retry in this server.

The git history contains the isolation tests for each of these.

Install

Requires Python 3.11 or newer. 3.10 is not supported: it reached end of life in October 2026, and Google's client libraries have stopped releasing for it.

uv venv
uv pip install -e .

Setup

1. Google Cloud project (once, ~5 minutes)

  1. Create a project at https://console.cloud.google.com/.
  2. Enable the Blogger API v3 for it (APIs & Services → Library → "Blogger API v3" → Enable).
  3. Configure the OAuth consent screen:
    • User type External
    • Add the scope https://www.googleapis.com/auth/blogger
    • Add your own Google account as a test user
  4. APIs & Services → Credentials → Create credentials → OAuth client ID → application type Desktop app. Download the JSON.
  5. Save it as ~/.config/blogger-mcp/client_secret.json (or point the BLOGGER_MCP_CLIENT_SECRETS environment variable at wherever you put it).

Expect to re-authorize about once a week. While an external app's publishing status is Testing, Google issues refresh tokens that expire after 7 days. When that happens the server returns an error saying so; run blogger-mcp-auth again. See below for why leaving Testing is harder than it looks.

About the 7-day expiry

Testing status also means only accounts listed as test users on the consent screen can authorize at all (up to 100 of them), so add anyone who needs access there.

Escaping the 7-day expiry means publishing the consent screen to Production, and Google requires an app homepage and a privacy policy to do that. Those have to be on a domain you own and have verified in Search Console — Google explicitly rules out platforms where you cannot prove you own the subdomain, which excludes a Blogger blog, GitHub Pages, Google Sites and similar. The Blogger scope is broad enough to count as sensitive, so a published app also shows a "Google hasn't verified this app" warning until it passes review, which wants a demo video and takes days to weeks.

Google's documentation ties the 7-day expiry specifically to Testing status, which implies publishing alone is enough. Reports from developers of apps that are in Production but unverified are mixed, and this project has not tested it. Do not assume publishing will fix it.

For a personal setup, re-authorizing weekly is usually less work than owning a domain to satisfy a review process. If you do want to go through with it, you need a real domain, a page describing the app, and a privacy policy on that same domain.

2. Authorize (once)

blogger-mcp-auth

This opens a browser, asks you to sign in and consent, then caches the token at ~/.config/blogger-mcp/token.json (mode 0600). It finishes by printing the blogs on the account together with their blog ids — you will need one of those ids for every call.

Re-run it any time you need to re-authorize or switch account.

The consent flow lives in this separate command rather than in the server on purpose: opening a browser from inside the server would block the stdio transport, and the client would just see a server that never responds.

3. Register the server with your MCP client

Claude Code:

claude mcp add blogger -- /path/to/blogger/.venv/bin/blogger-mcp

Or in a client config file (Claude Desktop's claude_desktop_config.json):

{
  "mcpServers": {
    "blogger": {
      "command": "/path/to/blogger/.venv/bin/blogger-mcp"
    }
  }
}

The server never opens a browser. If the cached token is missing or broken it returns an error telling you to run blogger-mcp-auth.

Configuration

All optional:

VariableDefaultPurpose
BLOGGER_MCP_HOME~/.config/blogger-mcpDirectory for credentials
BLOGGER_MCP_CLIENT_SECRETS$BLOGGER_MCP_HOME/client_secret.jsonOAuth client JSON
BLOGGER_MCP_TOKEN$BLOGGER_MCP_HOME/token.jsonCached token
BLOGGER_MCP_READONLYunsetSet to 1 to request the read-only scope

To manage two Google accounts, run two server instances with different BLOGGER_MCP_TOKEN paths.

Design decisions

blog_id is always an explicit argument. The OAuth token decides which account the server acts as; blog_id decides which blog a call touches. The server never defaults or remembers a blog — the caller is expected to know which blog it is operating on. list_blogs exists to discover ids, not as a required first step.

Writes are safe by default. create_post and create_page produce drafts. Publishing is always a separate, explicit publish_post call. The intent is that "organize a document" and "put it on the public internet" are never the same action — which matters more than usual when the caller is a language model. Deletes report what actually happened — delete_post reads the post back afterwards and says whether it went to the trash (restorable with revert_post) or is gone for good, because that depends on the post's state at the time. revert_post remains the way to take a post down without risking anything.

Errors carry their remediation. Anticipated failures are raised as ToolError so the message survives to the caller. Requests are built inside the error handler as well as executed there, because the client library validates enum arguments while building — a mistake there would otherwise surface as an opaque "error executing tool".

Agent guidance (the Skill)

skills/blogger-publishing/SKILL.md is an Agent Skill describing the intended workflows: draft → review → publish, safely updating an existing post, and what to do about the image limitation. The MCP server gives an agent the tools; the Skill tells it how to use them well.

The Skill is only useful alongside the MCP server — install both in whichever client you use.

Claude Code — copy or symlink the folder into your skills directory:

ln -s "$PWD/skills/blogger-publishing" ~/.claude/skills/blogger-publishing

Claude Desktop / claude.ai — build the ZIP and upload it:

./build-skill.sh          # writes dist/blogger-publishing.zip

Then in Claude: Settings → Capabilities → enable code execution (skills require it), then Customize → Skills → + and upload dist/blogger-publishing.zip.

The ZIP deliberately contains blogger-publishing/ as its root entry, which is the structure the uploader expects. Note that description: in the frontmatter is capped at 200 characters — keep it under that if you edit it, or the upload is rejected.

License

MIT — see LICENSE.

↑↓ navigate openesc close