hashnode-mcp-server
This MCP server lets you manage a Hashnode blog through the Hashnode GraphQL API.
Retrieve your profile and publication IDs.
Get publication metadata by host/domain.
List published posts and fetch a post by slug.
List drafts in a publication.
Create and update drafts with Markdown content, tags, cover image, slug, and SEO fields.
Publish drafts or publish posts directly, including scheduled publishing.
Update published posts.
Permanently delete published posts.
Allows managing a Hashnode blog, including creating drafts, publishing posts, listing and updating posts, and managing publications via the Hashnode GraphQL API.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hashnode-mcp-server Create a draft about cloud security best practices"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@kieksme/hashnode-mcp
MCP server for the Hashnode GraphQL API.
Create drafts, publish posts, and manage your blog from any MCP client.
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.
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 |
| Get your profile and publication IDs |
| Get publication info by host |
| List published posts |
| Get a single post by slug |
| Create a draft |
| Update an existing draft |
| List drafts in a publication |
| Publish a draft → live post |
| Publish directly (no draft step) |
| Update a published 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-mcpVia global install:
pnpm add -g @kieksme/hashnode-mcpBuild from source:
git clone https://github.com/kieksme/mcp-hashnode.git
cd mcp-hashnode
pnpm install && pnpm run build3. Configure your MCP client
Claude Code plugin
/plugin marketplace add kieksme/mcp-hashnode
/plugin install hashnode-mcp@mcp-hashnodeClaude 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-hereCursor
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_TOKENIn 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 |
|
# 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 startUpstream credentials
Variable | Notes |
| Default: process env Personal Access Token |
|
|
| Per-request PAT when mode is |
HTTP / kit env (summary)
Variable | Default | Notes |
|
|
|
|
| Preferred listen port (Railway). Fallback: |
|
| Use |
|
| MCP endpoint |
|
| Unauthenticated health |
| localhost trio | Required when binding beyond localhost |
|
|
|
| — | 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 startTry it locally with Jaeger (UI on http://localhost:16686):
docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/all-in-oneVariable | Notes |
| Switches tracing on (traces are posted to |
| Auth headers for the collector (never logged) |
| Service name, default |
| Kill switch |
| 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,HEALTHCHECKon/healthrailway.json— healthcheck path/healthserver.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-mcpUsage 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_postsAuthentication
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 buildNode 22.14+, pnpm, ESM.
Releases
This project uses release-please for automated releases.
Commits to
mainthat 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:
kieksmeRepository:
mcp-hashnodeWorkflow 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 toolshashnode_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 postBDestructive
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 publicationsARead-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 slugARead-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 hostARead-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 draftsARead-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 publicationARead-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
Scored across 11 tools
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.
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.
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.
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
Related MCP Connectors
Blog MCP server for AI agents. One signup call returns a blog, an API key and a live URL; then your agent publishes markdown posts and gets public URLs back. Your agent does the writing; you get a dashboard for stats.
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
Hosted Notion MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
MCP server for QPost — lets AI agents publish video and image posts to YouTube, TikTok, Instagram.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol server that enables AI assistants to programmatically interact with the Hashnode API for creating, updating, searching, and retrieving blog content.7MIT
- FlicenseAqualityDmaintenanceMCP server for blog admin API enabling preview, publish, list, and delete blog posts from Claude Code via stdio transport.5-
- AlicenseAqualityCmaintenanceMCP server for Substack that lets Claude Code create drafts, upload images, set cover thumbnails, schedule, and publish posts on your Substack publication.1116MIT
- AlicenseAqualityBmaintenanceA 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.250 npm1MIT