@promptingbox/mcp 0.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 +103 -0
- package/dist/api-client.d.ts +45 -0
- package/dist/api-client.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +189 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @promptingbox/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [PromptingBox](https://www.promptingbox.com) — save prompts directly from Claude, Cursor, ChatGPT, and other MCP-compatible AI tools.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Get your API key
|
|
8
|
+
|
|
9
|
+
Go to [PromptingBox Settings → MCP Integration](https://www.promptingbox.com/workspace/settings?view=mcp) and create an API key.
|
|
10
|
+
|
|
11
|
+
### 2. Configure your AI tool
|
|
12
|
+
|
|
13
|
+
> **Tip:** Name the server `pbox` so you can naturally say things like *"save this to pbox"* or *"list my pbox prompts"*.
|
|
14
|
+
|
|
15
|
+
#### Claude Desktop
|
|
16
|
+
|
|
17
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"pbox": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@promptingbox/mcp"],
|
|
25
|
+
"env": {
|
|
26
|
+
"PROMPTINGBOX_API_KEY": "pb_your_key_here"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### Cursor
|
|
34
|
+
|
|
35
|
+
Add to `.cursor/mcp.json` in your project (or global config):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"pbox": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["-y", "@promptingbox/mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"PROMPTINGBOX_API_KEY": "pb_your_key_here"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Claude Code
|
|
52
|
+
|
|
53
|
+
Add to `.claude/mcp.json` in your project:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"pbox": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@promptingbox/mcp"],
|
|
61
|
+
"env": {
|
|
62
|
+
"PROMPTINGBOX_API_KEY": "pb_your_key_here"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Restart your AI tool
|
|
70
|
+
|
|
71
|
+
Restart Claude Desktop or Cursor for the MCP server to be detected.
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
Once configured, you can say things like:
|
|
76
|
+
|
|
77
|
+
- "Save this prompt to pbox"
|
|
78
|
+
- "Save this as 'Code Review Checklist' in my Work folder on pbox"
|
|
79
|
+
- "List my pbox prompts"
|
|
80
|
+
- "List my pbox folders"
|
|
81
|
+
- "List my pbox tags"
|
|
82
|
+
- "Move prompt [id] to my Work folder on pbox"
|
|
83
|
+
|
|
84
|
+
## Available Tools
|
|
85
|
+
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `save_prompt` | Save a prompt with title, content, optional folder and tags |
|
|
89
|
+
| `list_prompts` | List all your prompts with their current folder assignment |
|
|
90
|
+
| `list_folders` | List your PromptingBox folders |
|
|
91
|
+
| `list_tags` | List your PromptingBox tags |
|
|
92
|
+
| `move_prompt_to_folder` | Move an existing prompt to a different folder by prompt ID |
|
|
93
|
+
|
|
94
|
+
## Environment Variables
|
|
95
|
+
|
|
96
|
+
| Variable | Required | Description |
|
|
97
|
+
|----------|----------|-------------|
|
|
98
|
+
| `PROMPTINGBOX_API_KEY` | Yes | Your PromptingBox API key (starts with `pb_`) |
|
|
99
|
+
| `PROMPTINGBOX_BASE_URL` | No | Override the API base URL (default: `https://www.promptingbox.com`) |
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface PromptingBoxConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SavePromptParams {
|
|
6
|
+
title: string;
|
|
7
|
+
content: string;
|
|
8
|
+
folder?: string;
|
|
9
|
+
tagNames?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface SavePromptResult {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
folderId: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Folder {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
order: number;
|
|
22
|
+
}
|
|
23
|
+
export interface Tag {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
export interface PromptListItem {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
folderId: string | null;
|
|
31
|
+
folderName: string | null;
|
|
32
|
+
}
|
|
33
|
+
export declare class PromptingBoxClient {
|
|
34
|
+
private apiKey;
|
|
35
|
+
private baseUrl;
|
|
36
|
+
constructor(config: PromptingBoxConfig);
|
|
37
|
+
private request;
|
|
38
|
+
savePrompt(params: SavePromptParams): Promise<SavePromptResult>;
|
|
39
|
+
listFolders(): Promise<Folder[]>;
|
|
40
|
+
listTags(): Promise<Tag[]>;
|
|
41
|
+
listPrompts(): Promise<PromptListItem[]>;
|
|
42
|
+
movePromptToFolder(promptId: string, folder: string): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = 'https://www.promptingbox.com';
|
|
2
|
+
export class PromptingBoxClient {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
8
|
+
}
|
|
9
|
+
async request(path, options) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const res = await fetch(url, {
|
|
12
|
+
...options,
|
|
13
|
+
headers: {
|
|
14
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
...options?.headers,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
const body = await res.text();
|
|
21
|
+
throw new Error(`PromptingBox API error ${res.status}: ${body}`);
|
|
22
|
+
}
|
|
23
|
+
return res.json();
|
|
24
|
+
}
|
|
25
|
+
async savePrompt(params) {
|
|
26
|
+
return this.request('/api/mcp/prompts', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: JSON.stringify(params),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async listFolders() {
|
|
32
|
+
return this.request('/api/mcp/folders');
|
|
33
|
+
}
|
|
34
|
+
async listTags() {
|
|
35
|
+
return this.request('/api/mcp/tags');
|
|
36
|
+
}
|
|
37
|
+
async listPrompts() {
|
|
38
|
+
return this.request('/api/mcp/prompts');
|
|
39
|
+
}
|
|
40
|
+
async movePromptToFolder(promptId, folder) {
|
|
41
|
+
return this.request(`/api/mcp/prompts/${promptId}/folder`, {
|
|
42
|
+
method: 'PATCH',
|
|
43
|
+
body: JSON.stringify({ folder }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
import { PromptingBoxClient } from './api-client.js';
|
|
6
|
+
const API_KEY = process.env.PROMPTINGBOX_API_KEY;
|
|
7
|
+
const BASE_URL = process.env.PROMPTINGBOX_BASE_URL; // optional override
|
|
8
|
+
if (!API_KEY) {
|
|
9
|
+
process.stderr.write('Error: PROMPTINGBOX_API_KEY environment variable is required.\n' +
|
|
10
|
+
'Get your API key at https://www.promptingbox.com/workspace/settings?view=mcp\n');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const client = new PromptingBoxClient({ apiKey: API_KEY, baseUrl: BASE_URL });
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: 'promptingbox',
|
|
16
|
+
version: '0.1.0',
|
|
17
|
+
});
|
|
18
|
+
// ── save_prompt ──────────────────────────────────────────────────────────────
|
|
19
|
+
server.tool('save_prompt', 'Save a prompt to the user\'s PromptingBox account. Use this when the user wants to save, store, or bookmark a prompt.', {
|
|
20
|
+
title: z.string().describe('A short, descriptive title for the prompt'),
|
|
21
|
+
content: z.string().describe('The full prompt content to save'),
|
|
22
|
+
folder: z.string().optional().describe('Folder name to save into (created if it doesn\'t exist)'),
|
|
23
|
+
tagNames: z.array(z.string()).optional().describe('Tag names to apply (created if they don\'t exist)'),
|
|
24
|
+
}, async ({ title, content, folder, tagNames }) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = await client.savePrompt({ title, content, folder, tagNames });
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: `Prompt saved to PromptingBox!\n\nTitle: ${result.title}\nID: ${result.id}\nURL: ${result.url}` +
|
|
32
|
+
(result.folderId ? `\nFolder: ${folder}` : ''),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: 'text', text: `Failed to save prompt: ${message}` }],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// ── list_folders ─────────────────────────────────────────────────────────────
|
|
46
|
+
server.tool('list_folders', 'List all folders in the user\'s PromptingBox account. Useful to know where to save a prompt.', {}, async () => {
|
|
47
|
+
try {
|
|
48
|
+
const folders = await client.listFolders();
|
|
49
|
+
if (folders.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: 'No folders found. You can specify a folder name when saving and it will be created automatically.' }],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const list = folders.map((f) => `- ${f.name}`).join('\n');
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: 'text', text: `Folders in PromptingBox:\n${list}` }],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: 'text', text: `Failed to list folders: ${message}` }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// ── list_prompts ─────────────────────────────────────────────────────────────
|
|
68
|
+
server.tool('list_prompts', 'List all prompts in the user\'s PromptingBox account grouped by folder. Use this to see what prompts exist and where they are organized.', {}, async () => {
|
|
69
|
+
try {
|
|
70
|
+
const prompts = await client.listPrompts();
|
|
71
|
+
if (prompts.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: 'text', text: 'No prompts found.' }],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Group by folder
|
|
77
|
+
const grouped = new Map();
|
|
78
|
+
for (const p of prompts) {
|
|
79
|
+
const key = p.folderName ?? '(no folder)';
|
|
80
|
+
if (!grouped.has(key))
|
|
81
|
+
grouped.set(key, []);
|
|
82
|
+
grouped.get(key).push(p);
|
|
83
|
+
}
|
|
84
|
+
// Sort: named folders first (alphabetical), then unfiled
|
|
85
|
+
const sortedKeys = [...grouped.keys()].sort((a, b) => {
|
|
86
|
+
if (a === '(no folder)')
|
|
87
|
+
return 1;
|
|
88
|
+
if (b === '(no folder)')
|
|
89
|
+
return -1;
|
|
90
|
+
return a.localeCompare(b);
|
|
91
|
+
});
|
|
92
|
+
const lines = [`Your PromptingBox prompts (${prompts.length} total):\n`];
|
|
93
|
+
for (const key of sortedKeys) {
|
|
94
|
+
lines.push(`📁 ${key}`);
|
|
95
|
+
for (const p of grouped.get(key)) {
|
|
96
|
+
lines.push(` • ${p.title} [id: ${p.id}]`);
|
|
97
|
+
}
|
|
98
|
+
lines.push('');
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: 'text', text: lines.join('\n').trimEnd() }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: 'text', text: `Failed to list prompts: ${message}` }],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// ── move_prompt_to_folder ─────────────────────────────────────────────────
|
|
113
|
+
server.tool('move_prompt_to_folder', 'Move a prompt to a different folder. Provide either the prompt ID or the prompt title — if a title is given, it will be looked up automatically. Does not change or delete prompt content.', {
|
|
114
|
+
promptId: z.string().optional().describe('The prompt ID (from list_prompts). Provide this or promptTitle.'),
|
|
115
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
116
|
+
folder: z.string().describe('The folder name to move the prompt into'),
|
|
117
|
+
}, async ({ promptId, promptTitle, folder }) => {
|
|
118
|
+
try {
|
|
119
|
+
let resolvedId = promptId;
|
|
120
|
+
if (!resolvedId) {
|
|
121
|
+
if (!promptTitle) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: 'Provide either promptId or promptTitle.' }],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const all = await client.listPrompts();
|
|
128
|
+
const lower = promptTitle.toLowerCase();
|
|
129
|
+
const matches = all.filter((p) => p.title.toLowerCase().includes(lower));
|
|
130
|
+
if (matches.length === 0) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: 'text', text: `No prompt found matching "${promptTitle}".` }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (matches.length > 1) {
|
|
137
|
+
const list = matches.map((p) => `- ${p.title} (id: ${p.id})`).join('\n');
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: 'text', text: `Multiple prompts match "${promptTitle}". Use promptId to be specific:\n${list}` }],
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
resolvedId = matches[0].id;
|
|
144
|
+
}
|
|
145
|
+
await client.movePromptToFolder(resolvedId, folder);
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: 'text', text: `Moved prompt to folder "${folder}".` }],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
+
return {
|
|
153
|
+
content: [{ type: 'text', text: `Failed to move prompt: ${message}` }],
|
|
154
|
+
isError: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
// ── list_tags ────────────────────────────────────────────────────────────────
|
|
159
|
+
server.tool('list_tags', 'List all tags in the user\'s PromptingBox account. Useful to know what tags are available when saving a prompt.', {}, async () => {
|
|
160
|
+
try {
|
|
161
|
+
const tags = await client.listTags();
|
|
162
|
+
if (tags.length === 0) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text', text: 'No tags found. You can specify tag names when saving and they will be created automatically.' }],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const list = tags.map((t) => `- ${t.name}`).join('\n');
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: 'text', text: `Tags in PromptingBox:\n${list}` }],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: 'text', text: `Failed to list tags: ${message}` }],
|
|
176
|
+
isError: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// ── start server ─────────────────────────────────────────────────────────────
|
|
181
|
+
async function main() {
|
|
182
|
+
const transport = new StdioServerTransport();
|
|
183
|
+
await server.connect(transport);
|
|
184
|
+
process.stderr.write('PromptingBox MCP server running on stdio\n');
|
|
185
|
+
}
|
|
186
|
+
main().catch((err) => {
|
|
187
|
+
process.stderr.write(`Fatal error: ${err}\n`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promptingbox/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for PromptingBox — save prompts from Claude, Cursor, and ChatGPT",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"promptingbox-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
20
|
+
"zod": "^3.24.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"typescript": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"prompts",
|
|
32
|
+
"claude",
|
|
33
|
+
"cursor",
|
|
34
|
+
"chatgpt",
|
|
35
|
+
"promptingbox",
|
|
36
|
+
"model-context-protocol"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/promptingbox/promptingbox"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://www.promptingbox.com"
|
|
43
|
+
}
|