Skip to main content
Glama
hashnode-mcp-server
by kieksme

@kieksme/hashnode-mcp

npm version CI Release AgentAvow MCP Trust Score

MCP server for the Hashnode GraphQL API.
Create drafts, publish posts, and manage your blog from any MCP client.

IMPORTANT

Hashnode Pro plan required.
Since May 2026, Hashnode's GraphQL API is only available to publications on a Pro plan.
Free accounts will receive an error. Upgrade at hashnode.com/settings/billing.

NOTE

Package rename: published as@kieksme/hashnode-mcp (was @kieksme/mcp-hashnode).
The mcp-hashnode CLI binary remains as a compatibility alias for hashnode-mcp.

Tools

Tool

Description

hashnode_get_me

Get your profile and publication IDs

hashnode_get_publication

Get publication info by host

hashnode_list_posts

List published posts

hashnode_get_post

Get a single post by slug

hashnode_create_draft

Create a draft

hashnode_update_draft

Update an existing draft

hashnode_list_drafts

List drafts in a publication

hashnode_publish_draft

Publish a draft → live post

hashnode_publish_post

Publish directly (no draft step)

hashnode_update_post

Update a published post

hashnode_delete_post

Delete a post ⚠️

Related MCP server: mcp-blog

Setup

1. Get your Personal Access Token

Go to hashnode.com/settings/developer and click Generate new token.

2. Install

Via npx (no install needed — recommended):

npx -y @kieksme/hashnode-mcp

Via global install:

pnpm add -g @kieksme/hashnode-mcp

Build from source:

git clone https://github.com/kieksme/mcp-hashnode.git
cd mcp-hashnode
pnpm install && pnpm run build

3. Configure your MCP client

Claude Code plugin

/plugin marketplace add kieksme/mcp-hashnode
/plugin install hashnode-mcp@mcp-hashnode

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "hashnode": {
      "command": "npx",
      "args": ["-y", "@kieksme/hashnode-mcp"],
      "env": {
        "HASHNODE_TOKEN": "your-token-here"
      }
    }
  }
}

Claude Code (CLI)

claude mcp add hashnode \
  --command npx \
  --args "-y @kieksme/hashnode-mcp" \
  --env HASHNODE_TOKEN=your-token-here

Cursor

Add to .cursor/mcp.json in your project root, or to ~/.cursor/mcp.json globally:

{
  "mcpServers": {
    "hashnode": {
      "command": "npx",
      "args": ["-y", "@kieksme/hashnode-mcp"],
      "env": {
        "HASHNODE_TOKEN": "your-token-here"
      }
    }
  }
}

VS Code (GitHub Copilot)

Add to .vscode/mcp.json in your workspace:

{
  "servers": {
    "hashnode": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@kieksme/hashnode-mcp"],
      "env": {
        "HASHNODE_TOKEN": "your-token-here"
      }
    }
  }
}

Remote MCP (ChatGPT, Codex, and Copilot Cloud Agent)

Set MCP_TRANSPORT=http, MCP_AUTH_TOKEN, MCP_HTTP_HOST=0.0.0.0, and MCP_ALLOWED_HOSTS on the hosted server (Railway sets PORT). The MCP endpoint is https://<your-host>/mcp; /health is available for hosting probes (no auth, Host-agnostic). Use the committed .github/mcp.json for Copilot Cloud Agent; it expects COPILOT_MCP_HASHNODE_URL and COPILOT_MCP_HASHNODE_TOKEN.

Legacy aliases still work (one-line deprecation warning): MCP_HTTP_AUTH_TOKEN, MCP_HTTP_PORT, MCP_HTTP_ALLOWED_HOSTS, MCP_HTTP_ALLOWED_ORIGINS, HEALTH_ENDPOINT.

In Codex, register the same endpoint with:

codex mcp add hashnode --url https://<your-host>/mcp \
  --bearer-token-env-var HASHNODE_MCP_AUTH_TOKEN

In ChatGPT, add the public HTTPS endpoint as a custom MCP app/connector and provide the bearer token during setup. ChatGPT cannot reach localhost.

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "hashnode": {
      "command": "npx",
      "args": ["-y", "@kieksme/hashnode-mcp"],
      "env": {
        "HASHNODE_TOKEN": "your-token-here"
      }
    }
  }
}

Running the server

Transport is provided by @kieksme/mcp-http-kit:

Mode

How

stdio (default)

Local MCP clients spawn the process

HTTP

MCP_TRANSPORT=http or --http

# Local stdio
HASHNODE_TOKEN=… npx -y @kieksme/hashnode-mcp

# Streamable HTTP (containers / Railway)
MCP_TRANSPORT=http \
MCP_HTTP_HOST=0.0.0.0 \
MCP_ALLOWED_HOSTS=your.railway.app \
MCP_AUTH_TOKEN="$(openssl rand -hex 32)" \
HASHNODE_TOKEN=… \
pnpm start

Upstream credentials

Variable

Notes

HASHNODE_TOKEN

Default: process env Personal Access Token

HASHNODE_CREDENTIAL_MODE

env (default) or header

X-Hashnode-Token

Per-request PAT when mode is header (never logged). Refused when MCP_AUTH_MODE=none.

HTTP / kit env (summary)

Variable

Default

Notes

MCP_TRANSPORT

stdio

stdio | http

PORT

3000

Preferred listen port (Railway). Fallback: MCP_HTTP_PORT

MCP_HTTP_HOST

127.0.0.1

Use 0.0.0.0 in containers

MCP_HTTP_PATH

/mcp

MCP endpoint

MCP_HEALTH_PATH

/health

Unauthenticated health

MCP_ALLOWED_HOSTS

localhost trio

Required when binding beyond localhost

MCP_AUTH_MODE

bearer

bearer | oauth | none

MCP_AUTH_TOKEN

—

Bearer secrets; comma-separated for rotation

Tracing (OpenTelemetry, opt-in)

Off by default. Set an OTLP endpoint and one tool call becomes one trace: MCP client → this server → Hashnode GraphQL API. The trace context comes from params._meta (traceparent / tracestate), so it also works over stdio. It is trusted only with MCP_AUTH_MODE bearer / oauth, or on stdio.

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_SERVICE_NAME=hashnode-mcp \
MCP_TRANSPORT=http MCP_AUTH_TOKEN=… HASHNODE_TOKEN=… \
pnpm start

Try it locally with Jaeger (UI on http://localhost:16686):

docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one

Variable

Notes

OTEL_EXPORTER_OTLP_ENDPOINT

Switches tracing on (traces are posted to <endpoint>/v1/traces). Use OTEL_EXPORTER_OTLP_TRACES_ENDPOINT for a full URL.

OTEL_EXPORTER_OTLP_HEADERS

Auth headers for the collector (never logged)

OTEL_SERVICE_NAME

Service name, default hashnode-mcp

OTEL_SDK_DISABLED=true

Kill switch

MCP_OTEL_RECORD_PAYLOADS=true

Also record tool arguments, results and full URLs. Off by default because they can contain article text and query strings.

Privacy by default: no tool arguments or results, no Authorization header, query strings and credentials removed from URL attributes, only traceparent / tracestate sent to Hashnode (never baggage), and only calls made inside a tool call are traced. Details: @kieksme/mcp-http-kit README.

Docker / Railway / Smithery

  • Dockerfile — multi-stage, non-root, HEALTHCHECK on /health

  • railway.json — healthcheck path /health

  • server.json — official MCP registry (io.github.kieksme/hashnode-mcp)

  • smithery.yaml — container runtime over Streamable HTTP

docker build -t hashnode-mcp .
docker run --rm -p 3000:3000 \
  -e MCP_AUTH_TOKEN=… \
  -e HASHNODE_TOKEN=… \
  -e MCP_ALLOWED_HOSTS=localhost,127.0.0.1 \
  hashnode-mcp

Usage examples

"What's my publication ID?"
→ hashnode_get_me

"Create a draft called 'Cloud Security Best Practices' with this content: ..."
→ hashnode_create_draft

"Publish the draft with ID xyz"
→ hashnode_publish_draft

"List my last 10 posts on thinkport.hashnode.dev"
→ hashnode_list_posts

Authentication

Hashnode requires Authorization: <token> (no Bearer prefix).
All mutations need the token; most read queries are public.

MCP clients talking to this server over HTTP must send Authorization: Bearer <MCP_AUTH_TOKEN> separately from the Hashnode PAT.

Rate limits

  • Queries: 20,000 req/min

  • Mutations: 500 req/min

Tags format

Tags must be objects — not plain strings:

[
  { "name": "Cloud Computing", "slug": "cloud-computing" },
  { "name": "DevOps", "slug": "devops" }
]

Development

pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build

Node 22.14+, pnpm, ESM.

Releases

This project uses release-please for automated releases.

  • Commits to main that follow Conventional Commits (feat:, fix:, chore: etc.) are tracked automatically.

  • release-please opens a Release PR that bumps the version and updates CHANGELOG.md.

  • Merging the Release PR creates a GitHub Release and triggers an automated npm publish of @kieksme/hashnode-mcp.

npm Trusted Publishing

The publish job uses npm Trusted Publishing (OIDC), so no NPM_TOKEN secret is needed. One-time setup on npmjs.com, under @kieksme/hashnode-mcp → Settings → Trusted Publisher:

  • Provider: GitHub Actions

  • Organization or user: kieksme

  • Repository: mcp-hashnode

  • Workflow filename: release.yml (must match exactly; leave Environment empty)

The workflow runs on a GitHub-hosted runner with Node 24 and publishes with npm 11.5.1 (npm publish --provenance).

License

MIT

Available Tools

11 tools
hashnode_create_draftCreate a Hashnode draftA

Create a new draft in a Hashnode publication. Does NOT publish — use hashnode_publish_draft afterwards.

Args:

  • publication_id (string, required): Publication ID

  • title (string, required): Post title

  • content_markdown (string, required): Post content in Markdown

  • subtitle (string, optional): Short subtitle

  • tags (array, optional): Tags as [{ name, slug }] — slug must be lowercase with hyphens

  • cover_image_url (string, optional): URL of cover image

  • slug (string, optional): Custom URL slug (auto-generated from title if omitted)

  • original_article_url (string, optional): Canonical URL for cross-posted articles

  • meta_title (string, optional): SEO meta title

  • meta_description (string, optional): SEO meta description

  • response_format: 'markdown' or 'json'

Returns: draft id, title, slug, tags, updatedAt

Inputs: title (required), publication_id (required), content_markdown (required), slug, +7 more.

hashnode_delete_postDelete a published Hashnode postB
Destructive

Permanently delete a published post. This action cannot be undone.

Args:

  • post_id (string, required): Post ID to delete

  • response_format: 'markdown' or 'json'

⚠️ WARNING: This permanently deletes the post.

Inputs: post_id (required), response_format.

hashnode_get_meGet current Hashnode user and their publicationsA
Read-onlyIdempotent

Returns the authenticated user's profile and a list of their publications. Use this first to discover your publicationId(s) before creating or listing posts.

Returns:

  • id, name, username, profilePicture

  • publications[]: { id, title, url }

Examples:

  • "What is my publication ID?" → call hashnode_get_me

  • "List my Hashnode blogs" → call hashnode_get_me

Inputs: response_format.

hashnode_get_postGet a single Hashnode post by slugA
Read-onlyIdempotent

Fetch full details of a published post by its slug.

Args:

  • host (string, required): Publication host

  • slug (string, required): Post slug from the URL, e.g. "my-first-post"

  • response_format: 'markdown' or 'json'

Returns: full post data including content brief, views, tags, cover image, author

Inputs: host (required), slug (required), response_format.

hashnode_get_publicationGet Hashnode publication by hostA
Read-onlyIdempotent

Fetch metadata for a Hashnode publication by its host/domain.

Args:

  • host (string): Publication host, e.g. "yourblog.hashnode.dev" or a custom domain

  • response_format: 'markdown' or 'json'

Returns: id, title, url, about, author info

Examples:

  • host: "thinkport.hashnode.dev"

Inputs: host (required), response_format.

hashnode_list_draftsList Hashnode draftsA
Read-onlyIdempotent

List drafts in a Hashnode publication.

Args:

  • publication_id (string): The publication ID (from hashnode_get_me)

  • limit (number): Number of drafts to return (default 20, max 50)

  • after (string): Pagination cursor from a previous call

  • response_format: 'markdown' or 'json'

Returns: list of drafts with id, title, subtitle, tags, updated date

Inputs: publication_id (required), after, limit, response_format.

hashnode_list_postsList published posts in a Hashnode publicationA
Read-onlyIdempotent

List published blog posts in a publication, ordered by publish date (newest first).

Args:

  • host (string, required): Publication host, e.g. "yourblog.hashnode.dev"

  • limit (number): Posts per page (default 20, max 50)

  • after (string): Cursor for pagination

  • response_format: 'markdown' or 'json'

Returns: posts with id, title, slug, url, publishedAt, views, readTime, tags

Inputs: host (required), after, limit, response_format.

hashnode_publish_draftPublish a Hashnode draftA

Publish an existing draft, making it a live blog post.

Args:

  • draft_id (string, required): Draft ID to publish (from hashnode_create_draft or hashnode_list_drafts)

  • response_format: 'markdown' or 'json'

Returns: post id, title, slug, url, publishedAt, tags

Examples:

  • Create draft → publish: hashnode_create_draft → hashnode_publish_draft

Inputs: draft_id (required), response_format.

hashnode_publish_postPublish a post directly to Hashnode (no draft step)A

Publish a new blog post directly without creating a draft first. Prefer hashnode_create_draft + hashnode_publish_draft for a safer workflow.

Args:

  • publication_id (string, required): Publication ID (from hashnode_get_me)

  • title (string, required): Post title

  • content_markdown (string, required): Post content in Markdown

  • subtitle (string, optional): Short subtitle

  • tags (array, optional): [{ name, slug }] — max 5 tags

  • cover_image_url (string, optional): URL of cover image

  • slug (string, optional): Custom URL slug

  • original_article_url (string, optional): Canonical URL for cross-posts

  • meta_title (string, optional): SEO title

  • meta_description (string, optional): SEO description

  • scheduled_at (string, optional): ISO 8601 datetime for scheduled publishing

  • response_format: 'markdown' or 'json'

Returns: post id, title, slug, url, publishedAt, tags

Inputs: title (required), publication_id (required), content_markdown (required), slug, +8 more.

hashnode_update_draftUpdate an existing Hashnode draftB

Update fields of an existing draft. Only provided fields are changed.

Args:

  • draft_id (string, required): Draft ID to update

  • title (string, optional): New title

  • content_markdown (string, optional): New content in Markdown

  • subtitle (string, optional): New subtitle

  • tags (array, optional): New tags as [{ name, slug }]

  • cover_image_url (string, optional): New cover image URL

  • response_format: 'markdown' or 'json'

Inputs: draft_id (required), tags, title, subtitle, +3 more.

hashnode_update_postUpdate a published Hashnode postA

Update fields of an already-published post. Only provided fields are changed.

Args:

  • post_id (string, required): Post ID (from hashnode_list_posts or hashnode_get_post)

  • title (string, optional): New title

  • content_markdown (string, optional): New content in Markdown

  • subtitle (string, optional): New subtitle

  • tags (array, optional): New tags [{ name, slug }]

  • cover_image_url (string, optional): New cover image URL

  • response_format: 'markdown' or 'json'

Inputs: post_id (required), tags, title, subtitle, +3 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 11 tools

    Disambiguation4/5

    Each tool maps to a distinct resource (publication, draft, post) plus action, so most selections are unambiguous. The only real overlap is the two publishing paths — hashnode_publish_post versus hashnode_create_draft + hashnode_publish_draft — though the descriptions explicitly recommend the latter for safety.

    Naming Consistency5/5

    Every tool uses the same hashnode_ prefix with a verb_noun structure (list_drafts, create_draft, publish_draft, update_post, delete_post). The lone outlier, hashnode_get_me, is still readable and consistent in style.

    Tool Count5/5

    At 11 tools the surface is well-scoped: two discovery/read tools, a full draft lifecycle, and full post CRUD. No tool looks redundant enough to remove, and nothing feels padded.

    Completeness4/5

    Posts have full lifecycle coverage (list, get, publish, update, delete) and drafts cover list/create/update/publish. The notable gap is the absence of a delete_draft (and any comment operations), which agents can work around but leaves drafts unmanageable once created.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      MCP server for Substack that lets Claude Code create drafts, upload images, set cover thumbnails, schedule, and publish posts on your Substack publication.
      11
      16
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      A generic MCP (Model Context Protocol) server that bridges any GraphQL API to Claude Code. It introspects your GraphQL schema and exposes each query and mutation as an individual tool, letting Claude interact with your API directly.
      2
      50 npm
      1
      MIT