Skip to main content
Glama
hatena-blog-mcp
by sumiVer2

hatena-blog-mcp

An MCP server for creating and updating Hatena Blog articles. It thinly wraps the Hatena Blog AtomPub API so that AI agents can draft, revise, and publish articles.

Setup

The package manager is pnpm (fixed via the packageManager field).

pnpm install   # 依存のインストールと同時に prepare で dist がビルドされる

Obtaining configuration values

The "Root endpoint" and "API key" are displayed under Hatena Blog's [Settings] > [Advanced Settings] > [AtomPub]. The root endpoint has the following format.

https://blog.hatena.ne.jp/{ブログ所有者のはてなID}/{ブログID}/atom

Environment variable

Required

Value

HATENA_ID

Yes

The Hatena ID of the account used for authentication (the owner of the API key)

HATENA_BLOG_ID

Yes

The {blog ID} part of the root endpoint (e.g. tech.example.hatenablog.com)

HATENA_API_KEY

Yes

API key

HATENA_BLOG_OWNER_ID

The {Hatena ID of the blog owner} part of the root endpoint. Defaults to HATENA_ID when omitted

  • For a blog you own, the owner and the operator are the same, so HATENA_BLOG_OWNER_ID is not needed

  • For a shared blog (such as a company tech blog), the owner and the operator differ. In that case, specify the blog owner's ID in HATENA_BLOG_OWNER_ID and your own account in HATENA_ID

  • Even if you use a custom domain on a paid plan, specify the domain before the custom domain setting in HATENA_BLOG_ID

See .env.example.

Registering with an MCP client

For Claude Code:

# 自分が所有するブログ
claude mcp add hatena-blog -s user \
  -e HATENA_ID=your-hatena-id \
  -e HATENA_BLOG_ID=your-blog.hatenablog.com \
  -e HATENA_API_KEY=your-api-key \
  -- node /absolute/path/to/hatena-blog-mcp/dist/index.js

# 共有ブログ(所有者と操作者が異なる場合は HATENA_BLOG_OWNER_ID を足す)
claude mcp add hatena-blog -s user \
  -e HATENA_ID=your-hatena-id \
  -e HATENA_BLOG_OWNER_ID=blog-owner-id \
  -e HATENA_BLOG_ID=blog-owner-id.hatenablog.com \
  -e HATENA_API_KEY=your-api-key \
  -- node /absolute/path/to/hatena-blog-mcp/dist/index.js

Adding -s user makes it available across all projects. Do not use -s project (the API key will be written to .mcp.json).

When writing directly to the configuration file:

{
  "mcpServers": {
    "hatena-blog": {
      "command": "node",
      "args": ["/absolute/path/to/tech-blog/dist/index.js"],
      "env": {
        "HATENA_ID": "your-hatena-id",
        "HATENA_BLOG_OWNER_ID": "blog-owner-id",
        "HATENA_BLOG_ID": "blog-owner-id.hatenablog.com",
        "HATENA_API_KEY": "your-api-key"
      }
    }
  }
}

Once registered, calling get_blog_info serves as a connectivity check.

Related MCP server: Blogger MCP Server

Tools

Blog-wide

Tool

Description

get_blog_info

Retrieves the blog title and available collections (also usable as a connectivity check)

list_categories

Lists the categories used in the blog

Entries

Tool

Description

list_entries

Lists entries in reverse chronological order (including drafts). Use next_page to fetch more

search_entries

Traverses pages and performs partial-match search on title, body, and categories

get_entry

Retrieves a single entry (the body remains in the registered notation)

create_entry

Creates a new entry (draft by default)

update_entry

Updates an entry with a diff-based update

Pages

list_pages / search_pages / get_page / create_page / update_page are provided in the same form as entries. Pages are only available on Hatena Blog paid plans (a 404 is returned on the free plan). Pages do not have categories, so there is no categories parameter.

Design decisions

Deletion is not implemented

Deletion of entries and pages (DELETE) exists on the API side but is intentionally not exposed as a tool. The impact of an accidental operation is too large and cannot be undone. Deletion should be done from the browser.

update_entry performs diff-based updates

Since AtomPub's PUT "replaces everything with the sent content," even when you only intend to fix the title, you must resend the body, categories, and post date/time in full. This server performs a GET followed by replacing only the specified items and then PUT inside update_entry.

  • Omitted items retain their current values

  • Omitting updated leaves the entry's post date/time (the displayed date) unchanged

  • Passing categories results in a replacement (not an addition). To keep existing categories, include them as well

New entries are drafts by default

The draft parameter of create_entry defaults to true. To prevent an agent operation from immediately publishing an entry, publishing only occurs when draft: false is explicitly specified.

Body notation

For content_type, you can specify text/x-markdown / text/x-hatena-syntax / text/html / text/plain (default is text/x-markdown). However, how it is actually interpreted follows the blog's "edit mode" setting, so it must be written to match the blog's settings. When updating an existing entry, the notation before editing is carried over.

Scheduled posting

Specify draft: true + scheduled: true + a future date/time for updated in create_entry.

List pagination

The Hatena Blog API returns few items per page, and the count is determined by the API side (the official documentation states 7 entries, but we have confirmed that 10 are actually returned). list_entries returns one page, and passing next_page as the page of the next call fetches more. To search across multiple entries at once, use search_entries, which traverses pages internally. Control the traversal amount with max_pages.

Authentication and the Hatena ID in the URL

WSSE authentication (the X-WSSE header) is used. A Nonce and Created are generated for each request, and Base64(SHA1(Nonce + Created + API key)) is sent as the PasswordDigest.

Note that the Hatena ID in the endpoint URL (the blog owner) and the account being authenticated are different things. Since API keys are issued per account rather than per blog, for a shared blog the combination is:

  • URL: https://blog.hatena.ne.jp/{owner's ID}/{blog ID}/atom

  • Authentication: your account's Hatena ID + API key

Confusing the two results in a 401 (the key does not belong to the owner) or a 403 (the account does not have permission for the blog). This server separates the two with HATENA_BLOG_OWNER_ID and HATENA_ID, and treats them as the same ID when omitted, so the same configuration method works for both your own blog and shared blogs.

Out of scope

  • Image upload: Outside the scope of AtomPub (the Hatena Fotolife API exists separately)

  • Changing page layouts: Not supported by the API. Configure from the browser

  • OAuth authentication: Only WSSE authentication with an API key is supported

Development

pnpm run typecheck   # 型チェック
pnpm test            # ユニットテスト(API はモック)
pnpm run build       # dist へビルド
pnpm run dev         # ビルドせずに起動
pnpm run inspect     # MCP Inspector で手動確認

Since pnpm 10 blocks dependency build scripts by default, only esbuild (used by tsx) is allowed via pnpm.onlyBuiltDependencies in package.json.

Structure

src/
  index.ts          エントリポイント(stdio トランスポート)
  server.ts         McpServer の組み立て
  config.ts         環境変数の読み込み
  hatena/
    client.ts       AtomPub の HTTP クライアント
    wsse.ts         WSSE 認証ヘッダの生成
    atom.ts         Atom XML のパース・生成
    types.ts        ドメイン型
  tools/
    blog.ts         ブログ全体に対するツール
    collection.ts   記事・固定ページ共通のツール定義
    shared.ts       ツールの共通ヘルパー

Since entries and pages have nearly the same structure on AtomPub, registerCollectionTools in tools/collection.ts is called with two different configurations: one for entries and one for pages.

License

MIT License. See LICENSE for details.

Available Tools

13 tools
create_entryブログ記事作成A

ブログ記事を新規作成する。既定では下書きとして作成されるので、公開する場合は draft を false にする。予約投稿する場合は draft=true・scheduled=true と、未来日時の updated を指定する。

Inputs: title (required), content (required), draft, updated, +5 more.

create_page固定ページ作成A

固定ページを新規作成する。既定では下書きとして作成されるので、公開する場合は draft を false にする。予約投稿する場合は draft=true・scheduled=true と、未来日時の updated を指定する。 固定ページははてなブログの有料プランでのみ利用できる。無料プランでは 404 が返る。

Inputs: title (required), content (required), draft, updated, +4 more.

get_blog_infoブログ情報取得A
Read-only

接続先ブログのタイトルと、利用できるコレクション(記事・固定ページ)を取得する。設定と認証が正しいかの疎通確認にも使える。固定ページのコレクションは有料プランでのみ返る。

Inputs: no named parameters.

get_entryブログ記事取得A
Read-only

ブログ記事を 1 件取得する。本文(登録記法のまま)・カテゴリ・公開状態を含む。編集前に必ずこれで現状を確認する。

Inputs: entry_id (required), include_formatted_content.

get_page固定ページ取得A
Read-only

固定ページを 1 件取得する。本文(登録記法のまま)・カテゴリ・公開状態を含む。編集前に必ずこれで現状を確認する。 固定ページははてなブログの有料プランでのみ利用できる。無料プランでは 404 が返る。

Inputs: page_id (required), include_formatted_content.

list_categoriesカテゴリ一覧A
Read-only

ブログで使われているカテゴリの一覧を取得する。記事を作成・更新する前に、既存のカテゴリ名と表記を揃えるために確認する。

Inputs: no named parameters.

list_entriesブログ記事一覧A
Read-only

ブログ記事を新しい順に一覧する。下書きも含まれる。1 ページあたりの件数は API 側で決まっており、続きを読むには戻り値の next_page を page に渡す。本文まで読みたい場合は get_entry を使う。

Inputs: page, include_content.

list_pages固定ページ一覧A
Read-only

固定ページを新しい順に一覧する。下書きも含まれる。1 ページあたりの件数は API 側で決まっており、続きを読むには戻り値の next_page を page に渡す。本文まで読みたい場合は get_page を使う。 固定ページははてなブログの有料プランでのみ利用できる。無料プランでは 404 が返る。

Inputs: page, include_content.

search_entriesブログ記事検索A
Read-only

ブログ記事をページを辿りながら検索する。タイトル・本文・カテゴリを対象に部分一致(大文字小文字は無視)。はてなブログの API は一覧の 1 ページあたり件数が少ないため、まとめて探すときはこのツールを使う。古いブログ記事まで届かない場合は max_pages を増やす。

Inputs: limit, query, status, category, +2 more.

search_pages固定ページ検索A
Read-only

固定ページをページを辿りながら検索する。タイトル・本文・カテゴリを対象に部分一致(大文字小文字は無視)。はてなブログの API は一覧の 1 ページあたり件数が少ないため、まとめて探すときはこのツールを使う。古い固定ページまで届かない場合は max_pages を増やす。 固定ページははてなブログの有料プランでのみ利用できる。無料プランでは 404 が返る。

Inputs: limit, query, status, max_pages, +1 more.

update_entryブログ記事更新A

ブログ記事を更新する。指定した項目だけを差し替える差分更新で、省略した項目は現在の値がそのまま残る。下書きを公開するには draft=false を指定する。categories を渡すと現在のカテゴリは完全に置き換わる。

Inputs: entry_id (required), draft, title, content, +5 more.

update_page固定ページ更新A

固定ページを更新する。指定した項目だけを差し替える差分更新で、省略した項目は現在の値がそのまま残る。下書きを公開するには draft=false を指定する。categories を渡すと現在のカテゴリは完全に置き換わる。 固定ページははてなブログの有料プランでのみ利用できる。無料プランでは 404 が返る。

Inputs: page_id (required), draft, title, content, +4 more.

upload_image画像アップロードA

ローカルの画像をはてなフォトライフにアップロードし、記事本文に貼れる記法(embed)を返す。embed は Markdown・はてな記法の編集モード向け。見たままモードのブログでは imageUrl を使う。画像は認証アカウント(HATENA_ID)のフォトライフに保存され、URL を知っていれば誰でも見られる。社外に出してよい画像だけを渡すこと。アップロードした画像の削除はブラウザから行う。

Inputs: file_path (required), title, folder.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 13 tools

    Disambiguation5/5

    Each tool maps to a distinct resource and action: entries, pages, categories, blog info, and image upload are cleanly separated. The list/search/get/create/update split is explicit, and descriptions clarify when to use list versus search.

    Naming Consistency5/5

    All tool names use consistent snake_case verb_noun patterns such as list_entries, get_entry, create_page, and update_page. Even the auxiliary tools get_blog_info, list_categories, and upload_image fit the same readable convention.

    Tool Count5/5

    With 13 tools, the set is well-scoped: entry CRUD/list/search, page CRUD/list/search, plus blog info, categories, and image upload. Each tool has a clear place, and the symmetric entries/pages coverage is reasonable rather than redundant.

    Completeness3/5

    The surface covers create, read, update, list, and search for both entries and pages, but deletion operations for entries and pages are missing. Image upload exists without an accompanying delete tool, with deletion explicitly delegated to the browser, leaving lifecycle gaps.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers