Content
# hatena-blog-mcp
English | [Japanese README](./README.md)
[Hatena Blog AtomPub API](https://developer.hatena.ne.jp/en/documents/blog/apis/atom) wrapped with **read and write support** as an MCP (Model Context Protocol) server. Designed to run on **Cloudflare Workers**, adopting a **BYOK (Bring Your Own Key)** model.
Originally created for the use case of "letting Claude reassign categories to all blog posts at once." Safely updates categories without accidentally changing titles, content, or post dates.
## Features
- Entries: `list_entries`, `get_entry`, `create_entry`, `update_entry`, `delete_entry`
- Pages: `list_pages`, `get_page`, `create_page`, `update_page`, `delete_page`
- Categories: `list_categories`
- **Safe partial updates**: `update_entry` / `update_page` preserve existing title, content, format, post date, and slug unless explicitly changed. The `content_type` always uses the existing entry's value, preventing silent Markdown to plain text switching.
- Server-side stateless — authentication information exists only in the `Authorization` header of each request.
## Transport
- **MCP Streamable HTTP only** (`POST /mcp`, JSON response mode)
- stdio not supported — for clients that only speak stdio, use [`mcp-remote`](https://github.com/geelen/mcp-remote)
- SSE not supported
## Endpoints
| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/mcp` | MCP Streamable HTTP entry point |
| `OPTIONS` | `/mcp` | CORS preflight |
| `GET` | `/` | Health check / server information in JSON |
---
## Quickstart: Deploy to Cloudflare Workers
```sh
pnpm install
pnpm exec wrangler login
pnpm exec wrangler deploy
```
No secrets, KV, or Durable Objects required — clients provide authentication information with each request. The URL will be in the form of `https://hatena-blog-mcp.<your-subdomain>.workers.dev`.
### Optional environment variables
| Variable | Default | Description |
| --- | --- | --- |
| `ALLOWED_ORIGINS` | _not set_ → `*` | Comma-separated list of CORS allowed origins (e.g., `https://claude.ai,https://chatgpt.com`). If not set, returns `Access-Control-Allow-Origin: *`. Authentication is done via `Authorization` header, making this secure. |
Example:
```sh
pnpm exec wrangler deploy --var ALLOWED_ORIGINS:"https://claude.ai,https://chatgpt.com"
```
---
## Authentication (BYOK)
This server **does not store any authentication information**. Each request must include:
```
Authorization: Basic base64(hatena_id:api_key)
```
API keys can be obtained from **Hatena Blog → Settings → Advanced Settings → AtomPub**. `hatena_id` is the left part of the blog URL (`<hatena_id>.hatenablog.com`).
Multiple users can share the same Worker and use their own API keys.
---
## Client Setup
### Claude Desktop / Claude.ai Web / Mobile (native remote MCP)
Add a new remote MCP server and specify the Worker URL:
- **URL**: `https://hatena-blog-mcp.<your-subdomain>.workers.dev/mcp`
- **Auth**: Basic authentication, username = Hatena ID, password = AtomPub API key
### Claude Code (or clients that only speak stdio) via `mcp-remote`
`mcp-remote` bridges stdio ↔ Streamable HTTP locally:
```jsonc
// ~/.claude.json or client configuration file
{
"mcpServers": {
"hatena-blog": {
"command": "npx",
"args": [
"mcp-remote",
"https://hatena-blog-mcp.<your-subdomain>.workers.dev/mcp",
"--header",
"Authorization: Basic ${BASIC_AUTH}"
],
"env": {
"BASIC_AUTH": "<base64(hatena_id:api_key)>"
}
}
}
}
```
Generate the Base64 value with `printf '%s' 'hatena_id:api_key' | base64`.
### MCP Inspector (for manual testing)
```sh
pnpm exec wrangler dev # run in a separate terminal
npx @modelcontextprotocol/inspector
```
Select **Streamable HTTP** in the Inspector, set the URL to `http://localhost:8787/mcp`, and add a custom header with `Authorization: Basic <base64>`.
---
## Tool Reference
All tools take `blog_id` (e.g., `example.hatenablog.com`) as a required argument and optionally `hatena_id`.
### Entries
| Name | Purpose | Required | Main options |
| --- | --- | --- | --- |
| `list_entries` | List entries (7 per page) | `blog_id` | `page`, `include_html` |
| `get_entry` | Get a single entry | `blog_id`, `entry_id` | `include_html` |
| `create_entry` | Create a new post | `blog_id`, `title`, `content` | `content_type`, `categories`, `draft`, `preview`, `scheduled`+`updated`, `custom_url` |
| `update_entry` | **Partial update** | `blog_id`, `entry_id` | `title`, `content`, `categories` (`[]` to clear), `draft`, `preview`, `custom_url`, `touch_updated` |
| `delete_entry` | Delete an entry | `blog_id`, `entry_id` | — |
`update_entry` specifications:
- Omitted fields preserve existing entry values.
- `content_type` is **always** obtained from the existing entry — cannot switch Markdown to plain text via this tool.
- `updated` is sent only when `touch_updated: true` (default: preserve post date).
- `custom_url` is sent only when explicitly specified (default: preserve existing slug).
### Pages
Similar to entries, but without `categories` and `scheduled`. `create_page` requires `custom_url`.
### Categories
- `list_categories` → `{ categories: string[], fixed: boolean }`. If `fixed: true`, new categories cannot be added to this blog.
---
## Example Use Case: Bulk Category Reassignment (the origin of this project)
After connecting the MCP server, simply ask Claude:
> "List all entries for my blog `example.hatenablog.com` using `list_entries`, read the content of each entry, and reorganize the existing categories. Do not change the title, content, or post date."
`update_entry` can be safely called by Claude:
```json
{
"name": "update_entry",
"arguments": {
"blog_id": "example.hatenablog.com",
"entry_id": "3000000000000000010",
"categories": ["Technology", "TypeScript", "Cloudflare"]
}
}
```
Without rewriting the content, switching Markdown to plain text, or changing the post date.
---
## Development
```sh
pnpm install
pnpm dev # start wrangler dev at http://localhost:8787
pnpm test # vitest
pnpm test:coverage # coverage report (overall 60%+, xml.ts 90%+)
pnpm lint # biome check
pnpm lint:fix # biome check --write
pnpm typecheck # tsc --noEmit
pnpm exec wrangler deploy --dry-run --outdir /tmp/out # bundle sanity check
```
### Directory Structure
```
src/
atompub/ — stateless HTTP client for Hatena AtomPub API
mcp/
tools/ — entries.ts, pages.ts, categories.ts (tool group files)
server.ts — createServer() registers 11 tools with a new McpServer
context.ts — request-specific authentication info + client generation
response.ts — ToolTextResult and Japanese error mapping
adapters/cloudflare/
index.ts — Hono app: CORS → BYOK auth → Streamable HTTP transport
utils/
auth.ts — parseBasicAuth
retry.ts — exponential backoff + jitter, respects Retry-After
test/
fixtures/ — sample real AtomPub responses
...
```
---
## Security Notes
- **This server relays `Authorization` headers.** Authentication information is decoded from the header, sent to the Worker, and then to Hatena. Not stored persistently, but still host in a trusted location. Malicious or compromised Workers can log or misuse all passing keys.
- **Logs intentionally exclude authentication information and response bodies.** `console.*` only contains status codes and error categories. Maintain this policy when adding logs.
- **CORS is open by default.** Authentication is done via `Authorization` headers (no CSRF vulnerabilities), and `Access-Control-Allow-Credentials` is not set, making this design secure. Set `ALLOWED_ORIGINS` to restrict origins.
- **No DNS rebinding protection.** Streamable HTTP transport checks are deprecated. Use a proxy to validate `Host` / `Origin` when exposing to an untrusted network.
- **Rate limiting and abuse protection.** Public deployments can be exposed to load. Consider Wrangler's `[limits]` block or WAF rate limiting rules if needed.
- **If an API key leaks**, revoke and reissue it from **Hatena Blog → Settings → Advanced Settings → AtomPub**. This server does not store or clear keys.
---
## License
MIT © Keisuke Nishitani
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
markitdown
Python tool for converting files and office documents to Markdown.
OpenAI Whisper
OpenAI Whisper MCP Server - 基于本地 Whisper CLI 的离线语音识别与翻译,无需 API Key,支持...
oh-my-opencode
Background agents · Curated agents like oracle, librarians, frontend...
claude-flow
Claude-Flow v2.7.0 is an enterprise AI orchestration platform.
ai-engineering-from-scratch
Learn it. Build it. Ship it for others. The most comprehensive open-source...
chatbox
User-friendly Desktop Client App for AI Models/LLMs (GPT, Claude, Gemini, Ollama...)