@kieksme/mcp-hashnode 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @kieksme/mcp-hashnode
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40kieksme%2Fmcp-hashnode)](https://www.npmjs.com/package/@kieksme/mcp-hashnode)
4
+ [![Release](https://github.com/kieksme/mcp-hashnode/actions/workflows/release.yml/badge.svg)](https://github.com/kieksme/mcp-hashnode/actions/workflows/release.yml)
5
+
6
+ MCP server for the [Hashnode](https://hashnode.com) GraphQL API.
7
+ Create drafts, publish posts, manage your blog — all via Claude.
8
+
9
+ ## Tools
10
+
11
+ | Tool | Description |
12
+ |---|---|
13
+ | `hashnode_get_me` | Get your profile and publication IDs |
14
+ | `hashnode_get_publication` | Get publication info by host |
15
+ | `hashnode_list_posts` | List published posts |
16
+ | `hashnode_get_post` | Get a single post by slug |
17
+ | `hashnode_create_draft` | Create a draft |
18
+ | `hashnode_update_draft` | Update an existing draft |
19
+ | `hashnode_list_drafts` | List drafts in a publication |
20
+ | `hashnode_publish_draft` | Publish a draft → live post |
21
+ | `hashnode_publish_post` | Publish directly (no draft step) |
22
+ | `hashnode_update_post` | Update a published post |
23
+ | `hashnode_delete_post` | Delete a post ⚠️ |
24
+
25
+ ## Setup
26
+
27
+ ### 1. Get your Personal Access Token
28
+
29
+ Go to [hashnode.com/settings/developer](https://hashnode.com/settings/developer) and click **Generate new token**.
30
+
31
+ ### 2. Install
32
+
33
+ **Via npx (no install needed — recommended):**
34
+ ```bash
35
+ npx -y @kieksme/mcp-hashnode
36
+ ```
37
+
38
+ **Via global install:**
39
+ ```bash
40
+ pnpm add -g @kieksme/mcp-hashnode
41
+ ```
42
+
43
+ **Build from source:**
44
+ ```bash
45
+ git clone https://github.com/kieksme/mcp-hashnode.git
46
+ cd mcp-hashnode
47
+ pnpm install && pnpm run build
48
+ ```
49
+
50
+ ### 3. Configure your MCP client
51
+
52
+ #### Claude Desktop
53
+
54
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "hashnode": {
60
+ "command": "npx",
61
+ "args": ["-y", "@kieksme/mcp-hashnode"],
62
+ "env": {
63
+ "HASHNODE_TOKEN": "your-token-here"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ #### Claude Code (CLI)
71
+
72
+ ```bash
73
+ claude mcp add hashnode \
74
+ --command npx \
75
+ --args "-y @kieksme/mcp-hashnode" \
76
+ --env HASHNODE_TOKEN=your-token-here
77
+ ```
78
+
79
+ #### Cursor
80
+
81
+ Add to `.cursor/mcp.json` in your project root, or to `~/.cursor/mcp.json` globally:
82
+
83
+ ```json
84
+ {
85
+ "mcpServers": {
86
+ "hashnode": {
87
+ "command": "npx",
88
+ "args": ["-y", "@kieksme/mcp-hashnode"],
89
+ "env": {
90
+ "HASHNODE_TOKEN": "your-token-here"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ #### VS Code (GitHub Copilot)
98
+
99
+ Add to `.vscode/mcp.json` in your workspace:
100
+
101
+ ```json
102
+ {
103
+ "servers": {
104
+ "hashnode": {
105
+ "type": "stdio",
106
+ "command": "npx",
107
+ "args": ["-y", "@kieksme/mcp-hashnode"],
108
+ "env": {
109
+ "HASHNODE_TOKEN": "your-token-here"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ #### Windsurf
117
+
118
+ Add to `~/.codeium/windsurf/mcp_config.json`:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "hashnode": {
124
+ "command": "npx",
125
+ "args": ["-y", "@kieksme/mcp-hashnode"],
126
+ "env": {
127
+ "HASHNODE_TOKEN": "your-token-here"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Usage examples
135
+
136
+ ```
137
+ "What's my publication ID?"
138
+ → hashnode_get_me
139
+
140
+ "Create a draft called 'Cloud Security Best Practices' with this content: ..."
141
+ → hashnode_create_draft
142
+
143
+ "Publish the draft with ID xyz"
144
+ → hashnode_publish_draft
145
+
146
+ "List my last 10 posts on thinkport.hashnode.dev"
147
+ → hashnode_list_posts
148
+ ```
149
+
150
+ ## Authentication
151
+
152
+ Hashnode requires `Authorization: <token>` (no `Bearer` prefix).
153
+ All mutations need the token; most read queries are public.
154
+
155
+ ## Rate limits
156
+
157
+ - Queries: 20,000 req/min
158
+ - Mutations: 500 req/min
159
+
160
+ ## Tags format
161
+
162
+ Tags must be objects — not plain strings:
163
+
164
+ ```json
165
+ [
166
+ { "name": "Cloud Computing", "slug": "cloud-computing" },
167
+ { "name": "DevOps", "slug": "devops" }
168
+ ]
169
+ ```
170
+
171
+ ## Releases
172
+
173
+ This project uses [release-please](https://github.com/googleapis/release-please) for automated releases.
174
+
175
+ - Commits to `main` that follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:` etc.) are tracked automatically.
176
+ - All supported Conventional Commit categories (`feat`, `fix`, `perf`, `revert`, `docs`, `style`, `chore`, `refactor`, `test`, `build`, `ci`) are included for release-please changelog generation and Release PR updates.
177
+ - release-please opens a **Release PR** that bumps the version and updates `CHANGELOG.md`.
178
+ - Merging the Release PR creates a **GitHub Release** and triggers an automated **npm publish**.
179
+
180
+ ### Required secret
181
+
182
+ Add `NPM_TOKEN` to the repository secrets (**Settings → Secrets → Actions**):
183
+ Generate at [npmjs.com/settings/tokens](https://www.npmjs.com/settings/tokens) — choose **Automation** type.
184
+
185
+ ## License
186
+
187
+ MIT
@@ -0,0 +1,3 @@
1
+ export declare const HASHNODE_GQL_ENDPOINT = "https://gql.hashnode.com";
2
+ export declare const CHARACTER_LIMIT = 25000;
3
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;AAChE,eAAO,MAAM,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const HASHNODE_GQL_ENDPOINT = "https://gql.hashnode.com";
2
+ export const CHARACTER_LIMIT = 25000;
3
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @kieksme/mcp-hashnode
4
+ *
5
+ * MCP server for the Hashnode GraphQL API.
6
+ * Supports creating drafts, publishing posts, listing/updating/deleting posts,
7
+ * and querying publications.
8
+ *
9
+ * Required env var: HASHNODE_TOKEN (Personal Access Token from hashnode.com/settings/developer)
10
+ *
11
+ * Usage (stdio):
12
+ * HASHNODE_TOKEN=<token> node dist/index.js
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG"}
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @kieksme/mcp-hashnode
4
+ *
5
+ * MCP server for the Hashnode GraphQL API.
6
+ * Supports creating drafts, publishing posts, listing/updating/deleting posts,
7
+ * and querying publications.
8
+ *
9
+ * Required env var: HASHNODE_TOKEN (Personal Access Token from hashnode.com/settings/developer)
10
+ *
11
+ * Usage (stdio):
12
+ * HASHNODE_TOKEN=<token> node dist/index.js
13
+ */
14
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import { setApiToken } from "./services/hashnode.js";
17
+ import { registerPublicationTools } from "./tools/publications.js";
18
+ import { registerDraftTools } from "./tools/drafts.js";
19
+ import { registerPostTools } from "./tools/posts.js";
20
+ // ─── Bootstrap ────────────────────────────────────────────────────────────────
21
+ const token = process.env.HASHNODE_TOKEN;
22
+ if (!token) {
23
+ console.error("ERROR: HASHNODE_TOKEN environment variable is required.\n" +
24
+ " Get your Personal Access Token at https://hashnode.com/settings/developer");
25
+ process.exit(1);
26
+ }
27
+ setApiToken(token);
28
+ // ─── Server ───────────────────────────────────────────────────────────────────
29
+ const server = new McpServer({
30
+ name: "@kieksme/mcp-hashnode",
31
+ version: "1.0.0",
32
+ });
33
+ registerPublicationTools(server);
34
+ registerDraftTools(server);
35
+ registerPostTools(server);
36
+ // ─── Transport ────────────────────────────────────────────────────────────────
37
+ async function main() {
38
+ const transport = new StdioServerTransport();
39
+ await server.connect(transport);
40
+ console.error("@kieksme/mcp-hashnode running via stdio");
41
+ }
42
+ main().catch((error) => {
43
+ console.error("Server error:", error instanceof Error ? error.message : String(error));
44
+ process.exit(1);
45
+ });
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,iFAAiF;AAEjF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;AACzC,IAAI,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CACX,2DAA2D;QACzD,kFAAkF,CACrF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,WAAW,CAAC,KAAK,CAAC,CAAC;AAEnB,iFAAiF;AAEjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAC3B,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAE1B,iFAAiF;AAEjF,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC3D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CACX,eAAe,EACf,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare function setApiToken(token: string): void;
2
+ /**
3
+ * Execute a GraphQL query or mutation against the Hashnode API.
4
+ * Auth header format: `Authorization: <token>` (NOT Bearer).
5
+ */
6
+ export declare function gql<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
7
+ /** Convert axios / GraphQL errors into actionable messages. */
8
+ export declare function normalizeError(error: unknown): Error;
9
+ //# sourceMappingURL=hashnode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashnode.d.ts","sourceRoot":"","sources":["../../src/services/hashnode.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE/C;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,CAAC,EACzB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAiCZ;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA6BpD"}
@@ -0,0 +1,61 @@
1
+ import axios, { AxiosError } from "axios";
2
+ import { HASHNODE_GQL_ENDPOINT } from "../constants.js";
3
+ let apiToken;
4
+ export function setApiToken(token) {
5
+ apiToken = token;
6
+ }
7
+ /**
8
+ * Execute a GraphQL query or mutation against the Hashnode API.
9
+ * Auth header format: `Authorization: <token>` (NOT Bearer).
10
+ */
11
+ export async function gql(query, variables) {
12
+ if (!apiToken) {
13
+ throw new Error("HASHNODE_TOKEN environment variable is not set. " +
14
+ "Get your Personal Access Token at https://hashnode.com/settings/developer");
15
+ }
16
+ try {
17
+ const response = await axios.post(HASHNODE_GQL_ENDPOINT, { query, variables }, {
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Accept: "application/json",
21
+ Authorization: apiToken,
22
+ },
23
+ timeout: 30_000,
24
+ });
25
+ if (response.data.errors?.length) {
26
+ const msgs = response.data.errors
27
+ .map((e) => e.message)
28
+ .join("; ");
29
+ throw new Error(`GraphQL error: ${msgs}`);
30
+ }
31
+ return response.data.data;
32
+ }
33
+ catch (error) {
34
+ throw normalizeError(error);
35
+ }
36
+ }
37
+ /** Convert axios / GraphQL errors into actionable messages. */
38
+ export function normalizeError(error) {
39
+ if (error instanceof AxiosError) {
40
+ if (error.response) {
41
+ switch (error.response.status) {
42
+ case 401:
43
+ return new Error("Error: Invalid or missing Hashnode token. " +
44
+ "Generate one at https://hashnode.com/settings/developer");
45
+ case 403:
46
+ return new Error("Error: Permission denied. Make sure your token has write access.");
47
+ case 429:
48
+ return new Error("Error: Rate limit exceeded (500 mutations/min). Please wait and retry.");
49
+ default:
50
+ return new Error(`Error: Hashnode API returned HTTP ${error.response.status}`);
51
+ }
52
+ }
53
+ if (error.code === "ECONNABORTED") {
54
+ return new Error("Error: Request timed out. Please try again.");
55
+ }
56
+ }
57
+ if (error instanceof Error)
58
+ return error;
59
+ return new Error(`Error: ${String(error)}`);
60
+ }
61
+ //# sourceMappingURL=hashnode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashnode.js","sourceRoot":"","sources":["../../src/services/hashnode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,IAAI,QAA4B,CAAC;AAEjC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,QAAQ,GAAG,KAAK,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,KAAa,EACb,SAAmC;IAEnC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,kDAAkD;YAChD,2EAA2E,CAC9E,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAC/B,qBAAqB,EACrB,EAAE,KAAK,EAAE,SAAS,EAAE,EACpB;YACE,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;gBAC1B,aAAa,EAAE,QAAQ;aACxB;YACD,OAAO,EAAE,MAAM;SAChB,CACF,CAAC;QAEF,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM;iBAC9B,GAAG,CAAC,CAAC,CAAsB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;iBAC1C,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAS,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,QAAQ,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAC9B,KAAK,GAAG;oBACN,OAAO,IAAI,KAAK,CACd,4CAA4C;wBAC1C,yDAAyD,CAC5D,CAAC;gBACJ,KAAK,GAAG;oBACN,OAAO,IAAI,KAAK,CACd,kEAAkE,CACnE,CAAC;gBACJ,KAAK,GAAG;oBACN,OAAO,IAAI,KAAK,CACd,wEAAwE,CACzE,CAAC;gBACJ;oBACE,OAAO,IAAI,KAAK,CACd,qCAAqC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAC7D,CAAC;YACN,CAAC;QACH,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAClC,OAAO,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IACD,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,IAAI,KAAK,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDraftTools(server: McpServer): void;
3
+ //# sourceMappingURL=drafts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../src/tools/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA+HpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkZ1D"}