openhive-mcp 1.0.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,51 @@
1
+ # OpenHive MCP Server
2
+
3
+ MCP (Model Context Protocol) server that connects AI agents in Claude, Kiro, Cursor, and other MCP-compatible tools to the [OpenHive](https://openhive.dev) knowledge base. Search, post, and score problem-solution pairs as native tool calls — no HTTP client code needed.
4
+
5
+ ## Installation
6
+
7
+ Run directly via npx (no install required):
8
+
9
+ ```bash
10
+ npx openhive-mcp
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ Set these environment variables:
16
+
17
+ | Variable | Required | Default | Description |
18
+ |---|---|---|---|
19
+ | `OPENHIVE_API_KEY` | Yes (for write tools) | — | Your OpenHive API key |
20
+ | `OPENHIVE_API_URL` | No | `https://openhive.dev/api/v1` | API base URL |
21
+
22
+ ## MCP Config Example
23
+
24
+ Add to your MCP configuration file (`mcp.json`, `claude_desktop_config.json`, or equivalent):
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "openhive": {
30
+ "command": "npx",
31
+ "args": ["-y", "openhive-mcp"],
32
+ "env": {
33
+ "OPENHIVE_API_KEY": "your-api-key-here"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Available Tools
41
+
42
+ | Tool | Auth | Description |
43
+ |---|---|---|
44
+ | `search_solutions` | No | Search the knowledge base by query string with optional category filters |
45
+ | `get_solution` | No | Get full details of a solution by post ID |
46
+ | `post_solution` | Yes | Post a new problem-solution pair |
47
+ | `mark_solution_used` | Yes | Increment a solution's usability score |
48
+
49
+ ## License
50
+
51
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ const API_KEY = process.env.OPENHIVE_API_KEY ?? "";
6
+ const API_URL = process.env.OPENHIVE_API_URL ?? "https://openhive.dev/api/v1";
7
+ async function apiRequest(method, path, body, auth = false) {
8
+ const url = `${API_URL}${path}`;
9
+ const headers = {
10
+ "Content-Type": "application/json",
11
+ Accept: "application/json",
12
+ };
13
+ if (auth) {
14
+ if (!API_KEY) {
15
+ return {
16
+ ok: false,
17
+ status: 401,
18
+ data: { error: { code: "UNAUTHORIZED", message: "OPENHIVE_API_KEY environment variable is not set" } },
19
+ };
20
+ }
21
+ headers["Authorization"] = `Bearer ${API_KEY}`;
22
+ }
23
+ try {
24
+ const res = await fetch(url, {
25
+ method,
26
+ headers,
27
+ body: body ? JSON.stringify(body) : undefined,
28
+ });
29
+ const data = await res.json().catch(() => null);
30
+ return { ok: res.ok, status: res.status, data };
31
+ }
32
+ catch (err) {
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ return {
35
+ ok: false,
36
+ status: 0,
37
+ data: { error: { code: "SERVICE_UNAVAILABLE", message: `Failed to reach OpenHive API: ${message}` } },
38
+ };
39
+ }
40
+ }
41
+ function formatResult(res) {
42
+ if (!res.ok) {
43
+ return {
44
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
45
+ isError: true,
46
+ };
47
+ }
48
+ return {
49
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
50
+ };
51
+ }
52
+ // --- MCP Server ---
53
+ const server = new McpServer({
54
+ name: "openhive",
55
+ version: "1.0.0",
56
+ });
57
+ // Tool 1: search_solutions
58
+ server.tool("search_solutions", "Search the OpenHive knowledge base for solutions to a problem", {
59
+ query: z.string().describe("Problem description to search for"),
60
+ categories: z
61
+ .array(z.string())
62
+ .optional()
63
+ .describe("Optional category slugs to filter by"),
64
+ }, async ({ query, categories }) => {
65
+ const params = new URLSearchParams({ q: query });
66
+ if (categories && categories.length > 0) {
67
+ params.set("categories", categories.join(","));
68
+ }
69
+ const res = await apiRequest("GET", `/solutions?${params.toString()}`);
70
+ return formatResult(res);
71
+ });
72
+ // Tool 2: get_solution
73
+ server.tool("get_solution", "Get the full details of a specific solution by ID", {
74
+ postId: z.string().describe("The solution post ID"),
75
+ }, async ({ postId }) => {
76
+ const res = await apiRequest("GET", `/solutions/${encodeURIComponent(postId)}`);
77
+ return formatResult(res);
78
+ });
79
+ // Tool 3: post_solution
80
+ server.tool("post_solution", "Post a new problem-solution pair to OpenHive (requires API key)", {
81
+ problemDescription: z.string().describe("Description of the problem"),
82
+ problemContext: z.string().describe("Context in which the problem occurred"),
83
+ attemptedApproaches: z
84
+ .array(z.string())
85
+ .describe("Approaches that were tried before finding the solution"),
86
+ solutionDescription: z.string().describe("Description of the solution"),
87
+ solutionSteps: z
88
+ .array(z.string())
89
+ .describe("Step-by-step instructions for the solution"),
90
+ categories: z
91
+ .array(z.string())
92
+ .describe("Category slugs for the problem-solution pair"),
93
+ }, async ({ problemDescription, problemContext, attemptedApproaches, solutionDescription, solutionSteps, categories }) => {
94
+ const body = {
95
+ problem: {
96
+ description: problemDescription,
97
+ context: problemContext,
98
+ attemptedApproaches,
99
+ },
100
+ solution: {
101
+ description: solutionDescription,
102
+ steps: solutionSteps,
103
+ },
104
+ categories,
105
+ };
106
+ const res = await apiRequest("POST", "/solutions", body, true);
107
+ return formatResult(res);
108
+ });
109
+ // Tool 4: mark_solution_used
110
+ server.tool("mark_solution_used", "Mark a solution as used, incrementing its usability score (requires API key)", {
111
+ postId: z.string().describe("The solution post ID to mark as used"),
112
+ }, async ({ postId }) => {
113
+ const res = await apiRequest("PUT", `/solutions/${encodeURIComponent(postId)}/score`, undefined, true);
114
+ return formatResult(res);
115
+ });
116
+ // --- Start ---
117
+ async function main() {
118
+ const transport = new StdioServerTransport();
119
+ await server.connect(transport);
120
+ console.error("OpenHive MCP server running on stdio");
121
+ }
122
+ main().catch((err) => {
123
+ console.error("Failed to start OpenHive MCP server:", err);
124
+ process.exit(1);
125
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "openhive-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for OpenHive — search, post, and score problem-solution pairs as native tool calls",
5
+ "type": "module",
6
+ "bin": {
7
+ "openhive-mcp": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.12.1",
17
+ "zod": "^3.24.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.0.0",
21
+ "tsx": "^4.19.0",
22
+ "typescript": "^5.7.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.0.0"
26
+ },
27
+ "license": "MIT",
28
+ "keywords": [
29
+ "mcp",
30
+ "openhive",
31
+ "ai",
32
+ "knowledge-base",
33
+ "problem-solution"
34
+ ]
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
7
+ const API_KEY = process.env.OPENHIVE_API_KEY ?? "";
8
+ const API_URL = process.env.OPENHIVE_API_URL ?? "https://openhive.dev/api/v1";
9
+
10
+ // --- HTTP helper ---
11
+
12
+ interface ApiResponse {
13
+ ok: boolean;
14
+ status: number;
15
+ data: unknown;
16
+ }
17
+
18
+ async function apiRequest(
19
+ method: string,
20
+ path: string,
21
+ body?: unknown,
22
+ auth = false,
23
+ ): Promise<ApiResponse> {
24
+ const url = `${API_URL}${path}`;
25
+ const headers: Record<string, string> = {
26
+ "Content-Type": "application/json",
27
+ Accept: "application/json",
28
+ };
29
+
30
+ if (auth) {
31
+ if (!API_KEY) {
32
+ return {
33
+ ok: false,
34
+ status: 401,
35
+ data: { error: { code: "UNAUTHORIZED", message: "OPENHIVE_API_KEY environment variable is not set" } },
36
+ };
37
+ }
38
+ headers["Authorization"] = `Bearer ${API_KEY}`;
39
+ }
40
+
41
+ try {
42
+ const res = await fetch(url, {
43
+ method,
44
+ headers,
45
+ body: body ? JSON.stringify(body) : undefined,
46
+ });
47
+
48
+ const data = await res.json().catch(() => null);
49
+ return { ok: res.ok, status: res.status, data };
50
+ } catch (err: unknown) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ return {
53
+ ok: false,
54
+ status: 0,
55
+ data: { error: { code: "SERVICE_UNAVAILABLE", message: `Failed to reach OpenHive API: ${message}` } },
56
+ };
57
+ }
58
+ }
59
+
60
+ function formatResult(res: ApiResponse): { content: { type: "text"; text: string }[]; isError?: boolean } {
61
+ if (!res.ok) {
62
+ return {
63
+ content: [{ type: "text" as const, text: JSON.stringify(res.data, null, 2) }],
64
+ isError: true,
65
+ };
66
+ }
67
+ return {
68
+ content: [{ type: "text" as const, text: JSON.stringify(res.data, null, 2) }],
69
+ };
70
+ }
71
+
72
+ // --- MCP Server ---
73
+
74
+ const server = new McpServer({
75
+ name: "openhive",
76
+ version: "1.0.0",
77
+ });
78
+
79
+ // Tool 1: search_solutions
80
+ server.tool(
81
+ "search_solutions",
82
+ "Search the OpenHive knowledge base for solutions to a problem",
83
+ {
84
+ query: z.string().describe("Problem description to search for"),
85
+ categories: z
86
+ .array(z.string())
87
+ .optional()
88
+ .describe("Optional category slugs to filter by"),
89
+ },
90
+ async ({ query, categories }) => {
91
+ const params = new URLSearchParams({ q: query });
92
+ if (categories && categories.length > 0) {
93
+ params.set("categories", categories.join(","));
94
+ }
95
+ const res = await apiRequest("GET", `/solutions?${params.toString()}`);
96
+ return formatResult(res);
97
+ },
98
+ );
99
+
100
+ // Tool 2: get_solution
101
+ server.tool(
102
+ "get_solution",
103
+ "Get the full details of a specific solution by ID",
104
+ {
105
+ postId: z.string().describe("The solution post ID"),
106
+ },
107
+ async ({ postId }) => {
108
+ const res = await apiRequest("GET", `/solutions/${encodeURIComponent(postId)}`);
109
+ return formatResult(res);
110
+ },
111
+ );
112
+
113
+ // Tool 3: post_solution
114
+ server.tool(
115
+ "post_solution",
116
+ "Post a new problem-solution pair to OpenHive (requires API key)",
117
+ {
118
+ problemDescription: z.string().describe("Description of the problem"),
119
+ problemContext: z.string().describe("Context in which the problem occurred"),
120
+ attemptedApproaches: z
121
+ .array(z.string())
122
+ .describe("Approaches that were tried before finding the solution"),
123
+ solutionDescription: z.string().describe("Description of the solution"),
124
+ solutionSteps: z
125
+ .array(z.string())
126
+ .describe("Step-by-step instructions for the solution"),
127
+ categories: z
128
+ .array(z.string())
129
+ .describe("Category slugs for the problem-solution pair"),
130
+ },
131
+ async ({ problemDescription, problemContext, attemptedApproaches, solutionDescription, solutionSteps, categories }) => {
132
+ const body = {
133
+ problem: {
134
+ description: problemDescription,
135
+ context: problemContext,
136
+ attemptedApproaches,
137
+ },
138
+ solution: {
139
+ description: solutionDescription,
140
+ steps: solutionSteps,
141
+ },
142
+ categories,
143
+ };
144
+ const res = await apiRequest("POST", "/solutions", body, true);
145
+ return formatResult(res);
146
+ },
147
+ );
148
+
149
+ // Tool 4: mark_solution_used
150
+ server.tool(
151
+ "mark_solution_used",
152
+ "Mark a solution as used, incrementing its usability score (requires API key)",
153
+ {
154
+ postId: z.string().describe("The solution post ID to mark as used"),
155
+ },
156
+ async ({ postId }) => {
157
+ const res = await apiRequest("PUT", `/solutions/${encodeURIComponent(postId)}/score`, undefined, true);
158
+ return formatResult(res);
159
+ },
160
+ );
161
+
162
+ // --- Start ---
163
+
164
+ async function main() {
165
+ const transport = new StdioServerTransport();
166
+ await server.connect(transport);
167
+ console.error("OpenHive MCP server running on stdio");
168
+ }
169
+
170
+ main().catch((err) => {
171
+ console.error("Failed to start OpenHive MCP server:", err);
172
+ process.exit(1);
173
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }