Micropub MCP
MCP server for creating, updating, and querying posts on Micropub-compatible blogs via IndieAuth
Ask AI about Micropub MCP
Powered by Claude Β· Grounded in docs
I know everything about Micropub MCP. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
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 |
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" }
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.
AI Transparency Metadata
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.
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
