MCP server that lets AI agents (Claude Code, Cursor, etc.) create, update, delete, and query posts on any Micropub-compatible blog. Authenticates via IndieAuth with PKCE.
Built with Bun + TypeScript.
git clone https://github.com/rmdes/micropub-mcp.git
cd micropub-mcp
bun installAdd to your .mcp.json (project or global):
{
"mcpServers": {
"micropub": {
"command": "bun",
"args": ["run", "/path/to/micropub-mcp/src/index.ts"]
}
}
}Tell Claude: "Authenticate with my blog at https://yourblog.com"
This calls micropub_auth, which:
- Discovers your blog's IndieAuth endpoints
- Opens your browser to the authorization page
- You approve access, the callback server catches the token
- Token is saved to
~/.config/micropub-mcp/<domain>.json
Authentication persists across sessions. You only need to do this once.
"Create a note saying Hello from my MCP client!"
"Write an article titled 'My Setup' about my development environment"
"Syndicate a note to Bluesky and Mastodon about the IndieWeb"
Authenticate with a Micropub-compatible blog via IndieAuth + PKCE.
| Parameter | Type | Required | Description |
|---|---|---|---|
site_url |
string | Yes | Your blog URL (e.g., https://yourblog.com) |
scope |
string | No | OAuth scopes (default: create update delete media) |
Create a new post (note, article, photo, bookmark, etc.).
| Parameter | Type | Required | Description |
|---|---|---|---|
content |
string | No | Post content (text or HTML) |
type |
string | No | Post type: entry (default), event |
name |
string | No | Title (makes it an article) |
summary |
string | No | Post summary |
category |
string[] | No | Tags/categories |
syndicate_to |
string[] | No | Syndication target UIDs |
in_reply_to |
string | No | URL being replied to |
like_of |
string | No | URL being liked |
repost_of |
string | No | URL being reposted |
photo |
string[] | No | Photo URLs |
slug |
string | No | URL slug |
post_status |
string | No | published or draft |
published |
string | No | ISO 8601 date |
ai_text_level |
string | No | AI text involvement: 0 (None), 1 (Editorial), 2 (Co-drafting), 3 (AI-generated) |
ai_code_level |
string | No | AI code involvement: 0 (Human), 1 (AI-assisted), 2 (Primarily AI) |
ai_tools |
string | No | AI tools used (e.g. "Claude", "ChatGPT, Copilot") |
ai_description |
string | No | Free-text description of AI usage |
Examples:
Create a note: { content: "Hello world" }
Create an article: { name: "My Title", content: "Article body..." }
Create a bookmark: { like_of: "https://example.com/post" }
Create a reply: { in_reply_to: "https://example.com/post", content: "Great post!" }
Syndicate: { content: "Cross-posted!", syndicate_to: ["https://brid.gy/publish/bluesky"] }
With AI metadata: { content: "AI-assisted post", ai_text_level: "2", ai_tools: "Claude" }
Update an existing post.
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | URL of the post to update |
replace |
object | No | Properties to replace (values are arrays) |
add |
object | No | Properties to add |
delete_properties |
string[] | No | Property names to remove |
Example: { url: "https://blog.com/notes/abc", replace: { content: ["Updated text"] }, add: { category: ["new-tag"] } }
Delete a post.
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | URL of the post to delete |
Restore a previously deleted post.
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | URL of the post to restore |
Query the Micropub server for configuration, posts, or metadata.
| Parameter | Type | Required | Description |
|---|---|---|---|
q |
string | Yes | Query type (see below) |
url |
string | No | Post URL (for source queries) |
properties |
string[] | No | Properties to return |
limit |
number | No | Max results |
offset |
number | No | Pagination offset |
Query types:
config— Server capabilities, syndication targets, post typessource— Get a post's properties (requiresurl)syndicate-to— Available syndication targetspost-types— Supported post typescategory— Available categories/tagschannel— Available channels
Upload a media file to the server's media endpoint.
| Parameter | Type | Required | Description |
|---|---|---|---|
file_path |
string | Yes | Absolute path to the file |
Returns the uploaded file's URL, which can be used in photo, video, or audio parameters of micropub_create.
Posts can optionally carry metadata disclosing AI involvement. These fields are sent as standard Micropub mf2 properties (ai-text-level, ai-code-level, ai-tools, ai-description).
All fields are optional. If your Micropub server doesn't know about them, it will simply store them as extra properties or ignore them — no special server-side support is required. This works with any standard Micropub endpoint.
| Level | ai_text_level |
ai_code_level |
|---|---|---|
| 0 | None — human wrote everything | Human-written |
| 1 | Editorial assistance (grammar, spelling) | AI-assisted |
| 2 | Co-drafting (human directed, AI wrote) | Primarily AI-generated |
| 3 | AI-generated (human reviewed) | — |
If your server does support these fields (e.g., Indiekit with @rmdes/indiekit-endpoint-posts), they can be displayed in the post form UI and used for filtering or labeling posts.
┌─────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ Claude │────>│ MCP Server │────>│ Browser │────>│ Blog │
│ Code │ │ (this tool) │ │ │ │ Server │
└─────────┘ └──────────────┘ └─────────────┘ └──────────┘
│ │ │ │
│ micropub_auth │ │ │
│───────────────>│ │ │
│ │ discover endpoints │ │
│ │────────────────────────────────────────>│
│ │ start callback │ │
│ │ server :19750 │ │
│ │ open browser ──────>│ │
│ │ │ authorize ────────>│
│ "open browser"│ │ │
│<───────────────│ │<── redirect with │
│ │ │ auth code │
│ │<── callback ────────│ │
│ │ exchange code ─────────────────────────>│
│ │<── access token ────────────────────────│
│ │ save token │ │
│ │ ~/.config/... │ │
The callback server runs on localhost:19750 and is a process-level singleton — it persists across MCP tool calls and shuts down after receiving the callback.
Tokens are saved to ~/.config/micropub-mcp/<domain>.json and include:
- Access token
- Refresh token (if provided)
- Expiration time
- Discovered endpoints (micropub, media, token)
Expired tokens are automatically refreshed if a refresh token is available.
- Bun 1.0+
- A blog with Micropub + IndieAuth support (e.g., Indiekit, WordPress with IndieWeb plugins, micro.blog)
If your blog runs Indiekit behind nginx, ensure:
-
CSP
form-action: The/authlocation must allowform-action *(or at minimumhttp://localhost:*) so the OAuth consent form can redirect to the local callback server. Without this, clicking "Allow" does nothing (HTTP 499 in logs). -
Redirect validation: Indiekit's upstream redirect regex (
/^\/[\w&/=?]*$/) rejects hyphens in paths like/auth/new-password. If you hitForbiddenError: Invalid redirect attempted, patchlib/indieauth.jswith an expanded regex.
# Run tests
bun test
# Start the MCP server (stdio)
bun run src/index.tssrc/
├── index.ts Entry point — creates MCP server, connects stdio transport
├── tools.ts 7 MCP tool definitions with Zod schemas
├── auth.ts IndieAuth + PKCE flow, token storage, non-blocking callback server
├── client.ts Micropub HTTP client (create/update/delete/query/upload)
└── discovery.ts Endpoint discovery from Link headers + HTML + indieauth-metadata
MIT