open-prompt-manager-mcp 3.0.3

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,105 @@
1
+ # open-prompt-manager-mcp (Node.js / npx)
2
+
3
+ A standalone MCP (Model Context Protocol) server that connects **Claude Desktop** to your running [Open Prompt Manager](https://github.com/ale-sanchez-g/open-prompt-manager) backend using **Node.js / npx**.
4
+
5
+ It communicates over **stdio** (required by Claude Desktop) and calls the backend REST API — no direct database access is needed.
6
+
7
+ ---
8
+
9
+ ## Requirements
10
+
11
+ - Node.js 24+
12
+ - A running Open Prompt Manager backend (default: `http://localhost:8000`)
13
+
14
+ ---
15
+
16
+ ## Claude Desktop configuration
17
+
18
+ Open your Claude Desktop config file:
19
+
20
+ | OS | Path |
21
+ |---------|------|
22
+ | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
23
+ | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
24
+
25
+ ### Recommended — point directly at the node binary (works with nvm)
26
+
27
+ Claude Desktop does **not** inherit your shell's `PATH` or `nvm` environment, so
28
+ always use the **full absolute path** to a Node 24+ binary.
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "open-prompt-manager": {
34
+ "command": "/absolute/path/to/node",
35
+ "args": ["/absolute/path/to/mcp-package-node/bin/server.js"],
36
+ "env": {
37
+ "BACKEND_URL": "http://localhost:8000"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ > **Tip:** find your node path with `which node` or `nvm which 21` (or whichever version ≥ 18 you use), and replace `/absolute/path/to/node` accordingly.
45
+
46
+ ### Option B — after publishing to npm
47
+
48
+ Once the package is published to the npm registry you can use npx:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "open-prompt-manager": {
54
+ "command": "/absolute/path/to/npx",
55
+ "args": ["-y", "open-prompt-manager-mcp"],
56
+ "env": {
57
+ "BACKEND_URL": "http://localhost:8000"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Restart Claude Desktop after saving the config.
65
+
66
+ ---
67
+
68
+ ## Environment variables
69
+
70
+ | Variable | Default | Description |
71
+ |---------------|---------------------------|------------------------------------------|
72
+ | `BACKEND_URL` | `http://localhost:8000` | Base URL of the Open Prompt Manager API |
73
+ | `API_KEY` | *(empty)* | Optional Bearer token for protected APIs |
74
+
75
+ ---
76
+
77
+ ## Local development
78
+
79
+ ```bash
80
+ cd mcp-package-node
81
+ npm install
82
+ node bin/server.js # waits for stdin from an MCP client — Ctrl+C to stop
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Available tools
88
+
89
+ | Tool | Description |
90
+ |------|-------------|
91
+ | `list_prompts` | List prompts with optional search / tag / agent filters |
92
+ | `get_prompt` | Retrieve a prompt by ID |
93
+ | `create_prompt` | Create a new prompt |
94
+ | `update_prompt` | Update an existing prompt |
95
+ | `delete_prompt` | Delete a prompt |
96
+ | `render_prompt` | Render a prompt with variable substitution |
97
+ | `list_tags` | List all tags |
98
+ | `create_tag` | Create a tag |
99
+ | `delete_tag` | Delete a tag |
100
+ | `list_agents` | List all agents |
101
+ | `get_agent` | Retrieve an agent by ID |
102
+ | `create_agent` | Create an agent |
103
+ | `update_agent` | Update an agent |
104
+ | `delete_agent` | Delete an agent |
105
+ | `health_check` | Verify the backend is reachable |
package/bin/server.js ADDED
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Open Prompt Manager — MCP server (stdio transport for Claude Desktop)
4
+ *
5
+ * Calls the Open Prompt Manager backend REST API over HTTP.
6
+ * No direct database access — the backend must be running.
7
+ *
8
+ * Environment variables:
9
+ * BACKEND_URL Base URL of the running backend (default: http://localhost:8000)
10
+ * API_KEY Optional Bearer token if your backend requires auth
11
+ */
12
+
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z } from "zod";
16
+ import { apiGet, apiPost, apiPut, apiDelete } from "../lib/helpers.js";
17
+
18
+ // ── MCP Server ────────────────────────────────────────────────────────────────
19
+
20
+ const server = new McpServer({
21
+ name: "Open Prompt Manager",
22
+ version: "0.1.0",
23
+ });
24
+
25
+ // ── Prompt tools ──────────────────────────────────────────────────────────────
26
+
27
+ server.tool(
28
+ "list_prompts",
29
+ "List available prompts, optionally filtered by search string, tag or agent.",
30
+ {
31
+ search: z.string().optional().describe("Substring to filter by name or description"),
32
+ tag_id: z.number().int().optional().describe("Filter prompts by tag ID"),
33
+ agent_id: z.number().int().optional().describe("Filter prompts by agent ID"),
34
+ skip: z.number().int().min(0).optional().default(0).describe("Records to skip (pagination)"),
35
+ limit: z.number().int().min(1).max(200).optional().default(50).describe("Max records to return"),
36
+ },
37
+ async ({ search, tag_id, agent_id, skip, limit }) => {
38
+ const result = await apiGet("/api/prompts/", { search, tag_id, agent_id, skip, limit });
39
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
40
+ }
41
+ );
42
+
43
+ server.tool(
44
+ "get_prompt",
45
+ "Retrieve a single prompt by its ID.",
46
+ { prompt_id: z.number().int().describe("The integer ID of the prompt") },
47
+ async ({ prompt_id }) => {
48
+ const result = await apiGet(`/api/prompts/${prompt_id}`);
49
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
50
+ }
51
+ );
52
+
53
+ server.tool(
54
+ "create_prompt",
55
+ "Create a new prompt.",
56
+ {
57
+ name: z.string().describe("Human-readable name for the prompt"),
58
+ content: z.string().describe("Prompt template content (supports {{variable}} placeholders)"),
59
+ description: z.string().optional().default("").describe("Optional description"),
60
+ version: z.string().optional().default("1.0.0").describe("Semantic version string"),
61
+ created_by: z.string().optional().default("").describe("Optional author identifier"),
62
+ variables: z.array(z.record(z.unknown())).optional().default([]).describe("Variable schema list"),
63
+ tag_ids: z.array(z.number().int()).optional().default([]).describe("Tag IDs to associate"),
64
+ agent_ids: z.array(z.number().int()).optional().default([]).describe("Agent IDs to associate"),
65
+ },
66
+ async ({ name, content, description, version, created_by, variables, tag_ids, agent_ids }) => {
67
+ const result = await apiPost("/api/prompts/", {
68
+ name, content, description, version, created_by, variables, tag_ids, agent_ids,
69
+ });
70
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
71
+ }
72
+ );
73
+
74
+ server.tool(
75
+ "update_prompt",
76
+ "Update an existing prompt. Only provided fields are changed.",
77
+ {
78
+ prompt_id: z.number().int().describe("ID of the prompt to update"),
79
+ name: z.string().optional().describe("New name"),
80
+ content: z.string().optional().describe("New template content"),
81
+ description: z.string().optional().describe("New description"),
82
+ variables: z.array(z.record(z.unknown())).optional().describe("Replacement variable schema list"),
83
+ tag_ids: z.array(z.number().int()).optional().describe("Replacement tag ID list"),
84
+ agent_ids: z.array(z.number().int()).optional().describe("Replacement agent ID list"),
85
+ },
86
+ async ({ prompt_id, ...fields }) => {
87
+ const payload = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
88
+ const result = await apiPut(`/api/prompts/${prompt_id}`, payload);
89
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
90
+ }
91
+ );
92
+
93
+ server.tool(
94
+ "delete_prompt",
95
+ "Delete a prompt by ID.",
96
+ { prompt_id: z.number().int().describe("The integer ID of the prompt to delete") },
97
+ async ({ prompt_id }) => {
98
+ const result = await apiDelete(`/api/prompts/${prompt_id}`);
99
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
100
+ }
101
+ );
102
+
103
+ server.tool(
104
+ "render_prompt",
105
+ "Render a prompt by substituting variables and resolving component references.",
106
+ {
107
+ prompt_id: z.number().int().describe("The integer ID of the prompt to render"),
108
+ variables: z.record(z.unknown()).optional().default({}).describe("Variable name → value pairs"),
109
+ },
110
+ async ({ prompt_id, variables }) => {
111
+ const result = await apiPost(`/api/prompts/${prompt_id}/render`, { variables });
112
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
113
+ }
114
+ );
115
+
116
+ // ── Tag tools ─────────────────────────────────────────────────────────────────
117
+
118
+ server.tool(
119
+ "list_tags",
120
+ "Return all tags defined in the system.",
121
+ {},
122
+ async () => {
123
+ const result = await apiGet("/api/tags/");
124
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
125
+ }
126
+ );
127
+
128
+ server.tool(
129
+ "create_tag",
130
+ "Create a new tag.",
131
+ {
132
+ name: z.string().describe("Unique label for the tag (e.g. \"safety\")"),
133
+ color: z.string().optional().default("#3B82F6").describe("Hex colour code used in the UI"),
134
+ },
135
+ async ({ name, color }) => {
136
+ const result = await apiPost("/api/tags/", { name, color });
137
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
138
+ }
139
+ );
140
+
141
+ server.tool(
142
+ "delete_tag",
143
+ "Delete a tag by ID.",
144
+ { tag_id: z.number().int().describe("The integer ID of the tag to delete") },
145
+ async ({ tag_id }) => {
146
+ const result = await apiDelete(`/api/tags/${tag_id}`);
147
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
148
+ }
149
+ );
150
+
151
+ // ── Agent tools ───────────────────────────────────────────────────────────────
152
+
153
+ server.tool(
154
+ "list_agents",
155
+ "Return all agents registered in the system.",
156
+ {},
157
+ async () => {
158
+ const result = await apiGet("/api/agents/");
159
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
160
+ }
161
+ );
162
+
163
+ server.tool(
164
+ "get_agent",
165
+ "Retrieve a single agent by its ID.",
166
+ { agent_id: z.number().int().describe("The integer ID of the agent") },
167
+ async ({ agent_id }) => {
168
+ const result = await apiGet(`/api/agents/${agent_id}`);
169
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
170
+ }
171
+ );
172
+
173
+ server.tool(
174
+ "create_agent",
175
+ "Create a new agent.",
176
+ {
177
+ name: z.string().describe("Human-readable name for the agent"),
178
+ description: z.string().optional().default("").describe("Optional description"),
179
+ type: z.string().optional().default("generic").describe("Agent type identifier"),
180
+ status: z.enum(["active", "inactive"]).optional().default("active").describe("Agent status"),
181
+ },
182
+ async ({ name, description, type, status }) => {
183
+ const result = await apiPost("/api/agents/", { name, description, type, status });
184
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
185
+ }
186
+ );
187
+
188
+ server.tool(
189
+ "update_agent",
190
+ "Update an existing agent. Only provided fields are changed.",
191
+ {
192
+ agent_id: z.number().int().describe("ID of the agent to update"),
193
+ name: z.string().optional().describe("New name"),
194
+ description: z.string().optional().describe("New description"),
195
+ type: z.string().optional().describe("New type identifier"),
196
+ status: z.enum(["active", "inactive"]).optional().describe("New status"),
197
+ },
198
+ async ({ agent_id, ...fields }) => {
199
+ const payload = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
200
+ const result = await apiPut(`/api/agents/${agent_id}`, payload);
201
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
202
+ }
203
+ );
204
+
205
+ server.tool(
206
+ "delete_agent",
207
+ "Delete an agent by ID.",
208
+ { agent_id: z.number().int().describe("The integer ID of the agent to delete") },
209
+ async ({ agent_id }) => {
210
+ const result = await apiDelete(`/api/agents/${agent_id}`);
211
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
212
+ }
213
+ );
214
+
215
+ // ── Health tool ───────────────────────────────────────────────────────────────
216
+
217
+ server.tool(
218
+ "health_check",
219
+ "Check that the Open Prompt Manager backend is reachable and healthy.",
220
+ {},
221
+ async () => {
222
+ const result = await apiGet("/api/health");
223
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
224
+ }
225
+ );
226
+
227
+ // ── Start ─────────────────────────────────────────────────────────────────────
228
+
229
+ const transport = new StdioServerTransport();
230
+ await server.connect(transport);
package/lib/helpers.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * HTTP helper functions for Open Prompt Manager MCP server.
3
+ * Configuration is read from environment variables at call time.
4
+ */
5
+
6
+ export function buildHeaders(apiKey = "") {
7
+ const h = { "Content-Type": "application/json", Accept: "application/json" };
8
+ if (apiKey) h["Authorization"] = `Bearer ${apiKey}`;
9
+ return h;
10
+ }
11
+
12
+ function getBackendUrl() {
13
+ return (process.env.BACKEND_URL ?? "http://localhost:8000").replace(/\/+$/, "");
14
+ }
15
+
16
+ function getApiKey() {
17
+ return process.env.API_KEY ?? "";
18
+ }
19
+
20
+ export async function apiGet(path, params) {
21
+ let url = `${getBackendUrl()}${path}`;
22
+ if (params) {
23
+ const qs = Object.entries(params)
24
+ .filter(([, v]) => v !== null && v !== undefined)
25
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
26
+ .join("&");
27
+ if (qs) url += `?${qs}`;
28
+ }
29
+ try {
30
+ const res = await fetch(url, { headers: buildHeaders(getApiKey()) });
31
+ if (!res.ok) return { error: `HTTP ${res.status}: ${await res.text()}` };
32
+ return res.json();
33
+ } catch (err) {
34
+ return { error: String(err) };
35
+ }
36
+ }
37
+
38
+ export async function apiPost(path, payload) {
39
+ try {
40
+ const res = await fetch(`${getBackendUrl()}${path}`, {
41
+ method: "POST",
42
+ headers: buildHeaders(getApiKey()),
43
+ body: JSON.stringify(payload),
44
+ });
45
+ if (!res.ok) return { error: `HTTP ${res.status}: ${await res.text()}` };
46
+ return res.json();
47
+ } catch (err) {
48
+ return { error: String(err) };
49
+ }
50
+ }
51
+
52
+ export async function apiPut(path, payload) {
53
+ try {
54
+ const res = await fetch(`${getBackendUrl()}${path}`, {
55
+ method: "PUT",
56
+ headers: buildHeaders(getApiKey()),
57
+ body: JSON.stringify(payload),
58
+ });
59
+ if (!res.ok) return { error: `HTTP ${res.status}: ${await res.text()}` };
60
+ return res.json();
61
+ } catch (err) {
62
+ return { error: String(err) };
63
+ }
64
+ }
65
+
66
+ export async function apiDelete(path) {
67
+ try {
68
+ const res = await fetch(`${getBackendUrl()}${path}`, {
69
+ method: "DELETE",
70
+ headers: buildHeaders(getApiKey()),
71
+ });
72
+ if (res.status === 204) return { success: true };
73
+ if (!res.ok) return { error: `HTTP ${res.status}: ${await res.text()}` };
74
+ return res.json();
75
+ } catch (err) {
76
+ return { error: String(err) };
77
+ }
78
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "open-prompt-manager-mcp",
3
+ "version": "3.0.3",
4
+ "description": "MCP server for Open Prompt Manager — connect Claude Desktop to your prompt library",
5
+ "type": "module",
6
+ "bin": {
7
+ "open-prompt-manager-mcp": "./bin/server.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/"
12
+ ],
13
+ "scripts": {
14
+ "start": "node bin/server.js",
15
+ "test": "node --test tests/server.test.js",
16
+ "lint:ci": "npx --yes oxlint@0.4.0 bin lib tests"
17
+ },
18
+ "keywords": ["mcp", "claude", "prompt-management", "ai"],
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.10.0",
22
+ "zod": "^3.25.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=24"
26
+ }
27
+ }