MCP server for creating, updating, and querying posts on Micropub-compatible blogs via IndieAuth
micropub-mcp
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.
Quick Start
1. Install
git clone https://github.com/rmdes/micropub-mcp.git
cd micropub-mcp
bun install
2. Add to Claude Code
Add to your .mcp.json (project or global):
{
"mcpServers": {
"micropub": {
"command": "bun",
"args": ["run", "/path/to/micropub-mcp/src/index.ts"]
}
}
}
3. Authenticate
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.
4. Start Posting
"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"
Tools
micropub_auth
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) |
micropub_create
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 |
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"] }
micropub_update
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"] } }
micropub_delete
Delete a post.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| url | string | Yes | URL of the post to delete |
micropub_undelete
Restore a previously deleted post.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| url | string | Yes | URL of the post to restore |
micropub_query
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
micropub_upload
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.
How Authentication Works
┌─────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐
│ 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.
Token Storage
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.
Requirements
- Bun 1.0+
- A blog with Micropub + IndieAuth support (e.g., Indiekit, WordPress with IndieWeb plugins, micro.blog)
Indiekit-Specific Notes
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.
Development
# Run tests
bun test
# Start the MCP server (stdio)
bun run src/index.ts
Architecture
src/
├── 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
License
MIT