@promptingbox/mcp 0.1.3 → 0.3.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 +73 -6
- package/dist/api-client.d.ts +116 -0
- package/dist/api-client.js +104 -5
- package/dist/index.js +518 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @promptingbox/mcp
|
|
2
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.
|
|
3
|
+
MCP (Model Context Protocol) server for [PromptingBox](https://www.promptingbox.com) — save, manage, and organize prompts directly from Claude, Cursor, ChatGPT, and other MCP-compatible AI tools.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -77,22 +77,89 @@ Restart Claude Desktop or Cursor for the MCP server to be detected.
|
|
|
77
77
|
|
|
78
78
|
Once configured, you can say things like:
|
|
79
79
|
|
|
80
|
+
**Saving & retrieving prompts:**
|
|
80
81
|
- "Save this prompt to pbox"
|
|
81
82
|
- "Save this as 'Code Review Checklist' in my Work folder on pbox"
|
|
83
|
+
- "Get my prompt called 'Code Review'"
|
|
84
|
+
- "Search my pbox prompts for API"
|
|
82
85
|
- "List my pbox prompts"
|
|
86
|
+
|
|
87
|
+
**Editing & managing prompts:**
|
|
88
|
+
- "Update the content of 'Code Review'"
|
|
89
|
+
- "Delete the prompt called 'Old Draft'"
|
|
90
|
+
- "Duplicate 'Code Review'"
|
|
91
|
+
- "Star my 'Code Review' prompt"
|
|
92
|
+
|
|
93
|
+
**Folders & tags:**
|
|
94
|
+
- "Create a folder called 'Work'"
|
|
95
|
+
- "Move 'Code Review' to my Work folder"
|
|
96
|
+
- "Delete my 'Old' folder"
|
|
97
|
+
- "Tag 'Code Review' with testing and automation"
|
|
83
98
|
- "List my pbox folders"
|
|
84
99
|
- "List my pbox tags"
|
|
85
|
-
|
|
100
|
+
|
|
101
|
+
**Version history:**
|
|
102
|
+
- "Show version history for 'Code Review'"
|
|
103
|
+
- "Restore version 2 of 'Code Review'"
|
|
104
|
+
|
|
105
|
+
**Templates:**
|
|
106
|
+
- "Search pbox templates for email"
|
|
107
|
+
- "Save that template to my collection"
|
|
108
|
+
|
|
109
|
+
**Account:**
|
|
110
|
+
- "Which pbox account am I using?"
|
|
86
111
|
|
|
87
112
|
## Available Tools
|
|
88
113
|
|
|
114
|
+
### Prompt Management
|
|
115
|
+
|
|
89
116
|
| Tool | Description |
|
|
90
117
|
|------|-------------|
|
|
91
118
|
| `save_prompt` | Save a prompt with title, content, optional folder and tags |
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
119
|
+
| `get_prompt` | Get the full content and metadata of a prompt |
|
|
120
|
+
| `search_prompts` | Search prompts by title, content, tag, folder, or favorites |
|
|
121
|
+
| `update_prompt` | Update a prompt's title and/or content (auto-versions) |
|
|
122
|
+
| `delete_prompt` | Permanently delete a prompt and all its versions |
|
|
123
|
+
| `duplicate_prompt` | Create a copy of an existing prompt |
|
|
124
|
+
| `toggle_favorite` | Star or unstar a prompt |
|
|
125
|
+
| `list_prompts` | List all prompts grouped by folder |
|
|
126
|
+
|
|
127
|
+
### Folder Management
|
|
128
|
+
|
|
129
|
+
| Tool | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `list_folders` | List all folders in your account |
|
|
132
|
+
| `create_folder` | Create a new folder (or return existing one) |
|
|
133
|
+
| `delete_folder` | Delete a folder (prompts move to root, not deleted) |
|
|
134
|
+
| `move_prompt_to_folder` | Move a prompt to a different folder |
|
|
135
|
+
|
|
136
|
+
### Tag Management
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|------|-------------|
|
|
140
|
+
| `list_tags` | List all tags in your account |
|
|
141
|
+
| `add_tags` | Set tags on a prompt (replaces existing, auto-creates new tags) |
|
|
142
|
+
| `delete_tag` | Delete a tag from your account and all prompts |
|
|
143
|
+
|
|
144
|
+
### Version History
|
|
145
|
+
|
|
146
|
+
| Tool | Description |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `list_versions` | View all saved versions of a prompt |
|
|
149
|
+
| `restore_version` | Restore a prompt to a previous version |
|
|
150
|
+
|
|
151
|
+
### Templates
|
|
152
|
+
|
|
153
|
+
| Tool | Description |
|
|
154
|
+
|------|-------------|
|
|
155
|
+
| `search_templates` | Browse and search the public template library |
|
|
156
|
+
| `use_template` | Save a public template to your collection |
|
|
157
|
+
|
|
158
|
+
### Account
|
|
159
|
+
|
|
160
|
+
| Tool | Description |
|
|
161
|
+
|------|-------------|
|
|
162
|
+
| `whoami` | Show which PromptingBox account is connected |
|
|
96
163
|
|
|
97
164
|
## Environment Variables
|
|
98
165
|
|
package/dist/api-client.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export interface SavePromptResult {
|
|
|
15
15
|
createdAt: string;
|
|
16
16
|
url: string;
|
|
17
17
|
}
|
|
18
|
+
export interface AccountInfo {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
18
23
|
export interface Folder {
|
|
19
24
|
id: string;
|
|
20
25
|
name: string;
|
|
@@ -29,6 +34,58 @@ export interface PromptListItem {
|
|
|
29
34
|
title: string;
|
|
30
35
|
folderId: string | null;
|
|
31
36
|
folderName: string | null;
|
|
37
|
+
isFavorite?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface PromptDetail {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string;
|
|
42
|
+
content: string;
|
|
43
|
+
folderId: string | null;
|
|
44
|
+
folderName: string | null;
|
|
45
|
+
isFavorite: boolean;
|
|
46
|
+
tags: Tag[];
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
}
|
|
50
|
+
export interface PromptVersion {
|
|
51
|
+
id: string;
|
|
52
|
+
versionNumber: number;
|
|
53
|
+
title: string;
|
|
54
|
+
content: string;
|
|
55
|
+
versionNote: string | null;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
}
|
|
58
|
+
export interface TemplateListItem {
|
|
59
|
+
id: string;
|
|
60
|
+
title: string;
|
|
61
|
+
description: string | null;
|
|
62
|
+
category: string;
|
|
63
|
+
icon: string | null;
|
|
64
|
+
usageCount: number;
|
|
65
|
+
}
|
|
66
|
+
export interface TemplateDetail {
|
|
67
|
+
id: string;
|
|
68
|
+
title: string;
|
|
69
|
+
content: string;
|
|
70
|
+
description: string | null;
|
|
71
|
+
category: string;
|
|
72
|
+
icon: string | null;
|
|
73
|
+
usageCount: number;
|
|
74
|
+
}
|
|
75
|
+
export interface TemplateSearchResult {
|
|
76
|
+
templates: TemplateListItem[];
|
|
77
|
+
pagination: {
|
|
78
|
+
page: number;
|
|
79
|
+
limit: number;
|
|
80
|
+
total: number;
|
|
81
|
+
hasMore: boolean;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export interface SearchPromptsParams {
|
|
85
|
+
search?: string;
|
|
86
|
+
tag?: string;
|
|
87
|
+
folder?: string;
|
|
88
|
+
favorites?: boolean;
|
|
32
89
|
}
|
|
33
90
|
export declare class PromptingBoxClient {
|
|
34
91
|
private apiKey;
|
|
@@ -42,4 +99,63 @@ export declare class PromptingBoxClient {
|
|
|
42
99
|
movePromptToFolder(promptId: string, folder: string): Promise<{
|
|
43
100
|
success: boolean;
|
|
44
101
|
}>;
|
|
102
|
+
getAccountInfo(): Promise<AccountInfo>;
|
|
103
|
+
getPrompt(id: string): Promise<PromptDetail>;
|
|
104
|
+
searchPrompts(params: SearchPromptsParams): Promise<PromptListItem[]>;
|
|
105
|
+
updatePrompt(id: string, updates: {
|
|
106
|
+
title?: string;
|
|
107
|
+
content?: string;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
success: boolean;
|
|
110
|
+
id: string;
|
|
111
|
+
versionCreated: boolean;
|
|
112
|
+
newVersionNumber: number | null;
|
|
113
|
+
}>;
|
|
114
|
+
deletePrompt(id: string): Promise<{
|
|
115
|
+
success: boolean;
|
|
116
|
+
}>;
|
|
117
|
+
duplicatePrompt(id: string): Promise<{
|
|
118
|
+
id: string;
|
|
119
|
+
title: string;
|
|
120
|
+
url: string;
|
|
121
|
+
}>;
|
|
122
|
+
toggleFavorite(id: string, isFavorite: boolean): Promise<{
|
|
123
|
+
success: boolean;
|
|
124
|
+
isFavorite: boolean;
|
|
125
|
+
}>;
|
|
126
|
+
updatePromptTags(promptId: string, tagNames: string[]): Promise<{
|
|
127
|
+
success: boolean;
|
|
128
|
+
tags: Tag[];
|
|
129
|
+
}>;
|
|
130
|
+
deleteTag(id: string): Promise<{
|
|
131
|
+
success: boolean;
|
|
132
|
+
tagName: string;
|
|
133
|
+
}>;
|
|
134
|
+
createFolder(name: string): Promise<{
|
|
135
|
+
id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
alreadyExisted: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
deleteFolder(id: string): Promise<{
|
|
140
|
+
success: boolean;
|
|
141
|
+
folderName: string;
|
|
142
|
+
}>;
|
|
143
|
+
listVersions(promptId: string): Promise<PromptVersion[]>;
|
|
144
|
+
restoreVersion(promptId: string, versionNumber: number): Promise<{
|
|
145
|
+
success: boolean;
|
|
146
|
+
restoredVersion: number;
|
|
147
|
+
newVersionNumber: number;
|
|
148
|
+
}>;
|
|
149
|
+
searchTemplates(params?: {
|
|
150
|
+
search?: string;
|
|
151
|
+
category?: string;
|
|
152
|
+
limit?: number;
|
|
153
|
+
page?: number;
|
|
154
|
+
}): Promise<TemplateSearchResult>;
|
|
155
|
+
getTemplate(id: string): Promise<TemplateDetail>;
|
|
156
|
+
useTemplate(templateId: string): Promise<{
|
|
157
|
+
promptId: string;
|
|
158
|
+
title: string;
|
|
159
|
+
url: string;
|
|
160
|
+
}>;
|
|
45
161
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -22,25 +22,124 @@ export class PromptingBoxClient {
|
|
|
22
22
|
}
|
|
23
23
|
return res.json();
|
|
24
24
|
}
|
|
25
|
+
// ── Existing methods ─────────────────────────────────────────────────────
|
|
25
26
|
async savePrompt(params) {
|
|
26
|
-
return this.request('/api/mcp/
|
|
27
|
+
return this.request('/api/mcp/prompt', {
|
|
27
28
|
method: 'POST',
|
|
28
29
|
body: JSON.stringify(params),
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
async listFolders() {
|
|
32
|
-
return this.request('/api/mcp/
|
|
33
|
+
return this.request('/api/mcp/folder');
|
|
33
34
|
}
|
|
34
35
|
async listTags() {
|
|
35
|
-
return this.request('/api/mcp/
|
|
36
|
+
return this.request('/api/mcp/tag');
|
|
36
37
|
}
|
|
37
38
|
async listPrompts() {
|
|
38
|
-
return this.request('/api/mcp/
|
|
39
|
+
return this.request('/api/mcp/prompt');
|
|
39
40
|
}
|
|
40
41
|
async movePromptToFolder(promptId, folder) {
|
|
41
|
-
return this.request(`/api/mcp/
|
|
42
|
+
return this.request(`/api/mcp/prompt/${promptId}/folder`, {
|
|
42
43
|
method: 'PATCH',
|
|
43
44
|
body: JSON.stringify({ folder }),
|
|
44
45
|
});
|
|
45
46
|
}
|
|
47
|
+
async getAccountInfo() {
|
|
48
|
+
return this.request('/api/mcp/me');
|
|
49
|
+
}
|
|
50
|
+
// ── New: Prompt operations ───────────────────────────────────────────────
|
|
51
|
+
async getPrompt(id) {
|
|
52
|
+
return this.request(`/api/mcp/prompt/${id}`);
|
|
53
|
+
}
|
|
54
|
+
async searchPrompts(params) {
|
|
55
|
+
const qs = new URLSearchParams();
|
|
56
|
+
if (params.search)
|
|
57
|
+
qs.set('search', params.search);
|
|
58
|
+
if (params.tag)
|
|
59
|
+
qs.set('tag', params.tag);
|
|
60
|
+
if (params.folder)
|
|
61
|
+
qs.set('folder', params.folder);
|
|
62
|
+
if (params.favorites)
|
|
63
|
+
qs.set('favorites', 'true');
|
|
64
|
+
const query = qs.toString();
|
|
65
|
+
return this.request(`/api/mcp/prompt${query ? `?${query}` : ''}`);
|
|
66
|
+
}
|
|
67
|
+
async updatePrompt(id, updates) {
|
|
68
|
+
return this.request(`/api/mcp/prompt/${id}`, {
|
|
69
|
+
method: 'PATCH',
|
|
70
|
+
body: JSON.stringify(updates),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async deletePrompt(id) {
|
|
74
|
+
return this.request(`/api/mcp/prompt/${id}`, {
|
|
75
|
+
method: 'DELETE',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async duplicatePrompt(id) {
|
|
79
|
+
return this.request(`/api/mcp/prompt/${id}/duplicate`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async toggleFavorite(id, isFavorite) {
|
|
84
|
+
return this.request(`/api/mcp/prompt/${id}/favorite`, {
|
|
85
|
+
method: 'PUT',
|
|
86
|
+
body: JSON.stringify({ isFavorite }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// ── New: Tag operations ──────────────────────────────────────────────────
|
|
90
|
+
async updatePromptTags(promptId, tagNames) {
|
|
91
|
+
return this.request(`/api/mcp/prompt/${promptId}/tag`, {
|
|
92
|
+
method: 'PUT',
|
|
93
|
+
body: JSON.stringify({ tagNames }),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async deleteTag(id) {
|
|
97
|
+
return this.request(`/api/mcp/tag/${id}`, {
|
|
98
|
+
method: 'DELETE',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// ── New: Folder operations ───────────────────────────────────────────────
|
|
102
|
+
async createFolder(name) {
|
|
103
|
+
return this.request('/api/mcp/folder', {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
body: JSON.stringify({ name }),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async deleteFolder(id) {
|
|
109
|
+
return this.request(`/api/mcp/folder/${id}`, {
|
|
110
|
+
method: 'DELETE',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// ── New: Version operations ──────────────────────────────────────────────
|
|
114
|
+
async listVersions(promptId) {
|
|
115
|
+
return this.request(`/api/mcp/prompt/${promptId}/version`);
|
|
116
|
+
}
|
|
117
|
+
async restoreVersion(promptId, versionNumber) {
|
|
118
|
+
return this.request(`/api/mcp/prompt/${promptId}/restore`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: JSON.stringify({ versionNumber }),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// ── New: Template operations ─────────────────────────────────────────────
|
|
124
|
+
async searchTemplates(params) {
|
|
125
|
+
const qs = new URLSearchParams();
|
|
126
|
+
if (params?.search)
|
|
127
|
+
qs.set('search', params.search);
|
|
128
|
+
if (params?.category)
|
|
129
|
+
qs.set('category', params.category);
|
|
130
|
+
if (params?.limit)
|
|
131
|
+
qs.set('limit', String(params.limit));
|
|
132
|
+
if (params?.page)
|
|
133
|
+
qs.set('page', String(params.page));
|
|
134
|
+
const query = qs.toString();
|
|
135
|
+
return this.request(`/api/mcp/template${query ? `?${query}` : ''}`);
|
|
136
|
+
}
|
|
137
|
+
async getTemplate(id) {
|
|
138
|
+
return this.request(`/api/mcp/template/${id}`);
|
|
139
|
+
}
|
|
140
|
+
async useTemplate(templateId) {
|
|
141
|
+
return this.request(`/api/mcp/template/${templateId}/use`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
46
145
|
}
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,100 @@ if (!API_KEY) {
|
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
const client = new PromptingBoxClient({ apiKey: API_KEY, baseUrl: BASE_URL });
|
|
14
|
+
const CURRENT_VERSION = '0.3.0';
|
|
15
|
+
// Cache account info so we can surface it in every response
|
|
16
|
+
let accountEmail = null;
|
|
17
|
+
async function getAccountLabel() {
|
|
18
|
+
if (accountEmail)
|
|
19
|
+
return accountEmail;
|
|
20
|
+
try {
|
|
21
|
+
const info = await client.getAccountInfo();
|
|
22
|
+
accountEmail = info.email;
|
|
23
|
+
return accountEmail;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return 'unknown (could not verify)';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// ── Version update check (once per process lifetime) ─────────────────────────
|
|
30
|
+
let updateNotice = null;
|
|
31
|
+
let updateChecked = false;
|
|
32
|
+
function isNewer(latest, current) {
|
|
33
|
+
const l = latest.split('.').map(Number);
|
|
34
|
+
const c = current.split('.').map(Number);
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
if ((l[i] ?? 0) > (c[i] ?? 0))
|
|
37
|
+
return true;
|
|
38
|
+
if ((l[i] ?? 0) < (c[i] ?? 0))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
async function checkForUpdate() {
|
|
44
|
+
if (updateChecked)
|
|
45
|
+
return updateNotice;
|
|
46
|
+
updateChecked = true;
|
|
47
|
+
try {
|
|
48
|
+
const versionUrl = `${BASE_URL || 'https://www.promptingbox.com'}/api/mcp/version`;
|
|
49
|
+
const res = await fetch(versionUrl);
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
return null;
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
const latest = data.mcp;
|
|
54
|
+
if (!latest)
|
|
55
|
+
return null;
|
|
56
|
+
if (isNewer(latest, CURRENT_VERSION)) {
|
|
57
|
+
updateNotice = [
|
|
58
|
+
``,
|
|
59
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
60
|
+
` New version available: v${CURRENT_VERSION} → v${latest}`,
|
|
61
|
+
` Run: npm install -g @promptingbox/mcp`,
|
|
62
|
+
` Changelog: https://www.promptingbox.com/docs/mcp`,
|
|
63
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
return updateNotice;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Combined response suffix: account label + update notice (if available) */
|
|
73
|
+
async function getResponseSuffix() {
|
|
74
|
+
const [email, update] = await Promise.all([
|
|
75
|
+
getAccountLabel(),
|
|
76
|
+
checkForUpdate(),
|
|
77
|
+
]);
|
|
78
|
+
let suffix = `🔑 Account: ${email}`;
|
|
79
|
+
if (update)
|
|
80
|
+
suffix += `\n${update}`;
|
|
81
|
+
return suffix;
|
|
82
|
+
}
|
|
83
|
+
/** Resolve promptId from either explicit ID or title search */
|
|
84
|
+
async function resolvePromptId(promptId, promptTitle) {
|
|
85
|
+
if (promptId)
|
|
86
|
+
return { id: promptId };
|
|
87
|
+
if (!promptTitle)
|
|
88
|
+
return { error: 'Provide either promptId or promptTitle.' };
|
|
89
|
+
const all = await client.listPrompts();
|
|
90
|
+
const lower = promptTitle.toLowerCase();
|
|
91
|
+
const matches = all.filter((p) => p.title.toLowerCase().includes(lower));
|
|
92
|
+
if (matches.length === 0)
|
|
93
|
+
return { error: `No prompt found matching "${promptTitle}".` };
|
|
94
|
+
if (matches.length > 1) {
|
|
95
|
+
const list = matches.map((p) => `- ${p.title} (id: ${p.id})`).join('\n');
|
|
96
|
+
return { error: `Multiple prompts match "${promptTitle}". Use promptId to be specific:\n${list}` };
|
|
97
|
+
}
|
|
98
|
+
return { id: matches[0].id };
|
|
99
|
+
}
|
|
100
|
+
function errorResult(message) {
|
|
101
|
+
return { content: [{ type: 'text', text: message }], isError: true };
|
|
102
|
+
}
|
|
14
103
|
const server = new McpServer({
|
|
15
104
|
name: 'promptingbox',
|
|
16
|
-
version:
|
|
105
|
+
version: CURRENT_VERSION,
|
|
17
106
|
});
|
|
107
|
+
const baseUrl = BASE_URL ?? 'https://www.promptingbox.com';
|
|
18
108
|
// ── save_prompt ──────────────────────────────────────────────────────────────
|
|
19
109
|
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
110
|
title: z.string().describe('A short, descriptive title for the prompt'),
|
|
@@ -23,54 +113,451 @@ server.tool('save_prompt', 'Save a prompt to the user\'s PromptingBox account. U
|
|
|
23
113
|
tagNames: z.array(z.string()).optional().describe('Tag names to apply (created if they don\'t exist)'),
|
|
24
114
|
}, async ({ title, content, folder, tagNames }) => {
|
|
25
115
|
try {
|
|
26
|
-
const result = await
|
|
116
|
+
const [result, suffix] = await Promise.all([
|
|
117
|
+
client.savePrompt({ title, content, folder, tagNames }),
|
|
118
|
+
getResponseSuffix(),
|
|
119
|
+
]);
|
|
27
120
|
return {
|
|
28
121
|
content: [
|
|
29
122
|
{
|
|
30
123
|
type: 'text',
|
|
31
124
|
text: `Prompt saved to PromptingBox!\n\nTitle: ${result.title}\nID: ${result.id}\nURL: ${result.url}` +
|
|
32
|
-
(result.folderId ? `\nFolder: ${folder}` : '')
|
|
125
|
+
(result.folderId ? `\nFolder: ${folder}` : '') +
|
|
126
|
+
`\n\n${suffix}`,
|
|
33
127
|
},
|
|
34
128
|
],
|
|
35
129
|
};
|
|
36
130
|
}
|
|
37
131
|
catch (err) {
|
|
38
132
|
const message = err instanceof Error ? err.message : String(err);
|
|
133
|
+
return errorResult(`Failed to save prompt: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// ── get_prompt ───────────────────────────────────────────────────────────────
|
|
137
|
+
server.tool('get_prompt', 'Get the full content of a single prompt from PromptingBox. Returns title, content, tags, folder, and metadata.', {
|
|
138
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
139
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
140
|
+
}, async ({ promptId, promptTitle }) => {
|
|
141
|
+
try {
|
|
142
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
143
|
+
if ('error' in resolved)
|
|
144
|
+
return errorResult(resolved.error);
|
|
145
|
+
const [prompt, suffix] = await Promise.all([
|
|
146
|
+
client.getPrompt(resolved.id),
|
|
147
|
+
getResponseSuffix(),
|
|
148
|
+
]);
|
|
149
|
+
const tagList = prompt.tags.length > 0
|
|
150
|
+
? `Tags: ${prompt.tags.map((t) => t.name).join(', ')}`
|
|
151
|
+
: 'Tags: (none)';
|
|
152
|
+
return {
|
|
153
|
+
content: [{
|
|
154
|
+
type: 'text',
|
|
155
|
+
text: `# ${prompt.title}\n\nID: ${prompt.id}\n` +
|
|
156
|
+
`Folder: ${prompt.folderName ?? '(none)'}\n` +
|
|
157
|
+
`${tagList}\n` +
|
|
158
|
+
`Favorite: ${prompt.isFavorite ? 'Yes' : 'No'}\n` +
|
|
159
|
+
`URL: ${baseUrl}/workspace/prompt/${prompt.id}\n\n` +
|
|
160
|
+
`---\n\n${prompt.content}\n\n${suffix}`,
|
|
161
|
+
}],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
166
|
+
return errorResult(`Failed to get prompt: ${message}`);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// ── search_prompts ──────────────────────────────────────────────────────────
|
|
170
|
+
server.tool('search_prompts', 'Search prompts in PromptingBox by title, content, tag, folder, or favorites. Returns matching prompts.', {
|
|
171
|
+
query: z.string().optional().describe('Search text to match against title and content'),
|
|
172
|
+
tag: z.string().optional().describe('Filter by tag name'),
|
|
173
|
+
folder: z.string().optional().describe('Filter by folder name'),
|
|
174
|
+
favorites: z.boolean().optional().describe('Set to true to only show favorited prompts'),
|
|
175
|
+
}, async ({ query, tag, folder, favorites }) => {
|
|
176
|
+
try {
|
|
177
|
+
const [results, suffix] = await Promise.all([
|
|
178
|
+
client.searchPrompts({ search: query, tag, folder, favorites }),
|
|
179
|
+
getResponseSuffix(),
|
|
180
|
+
]);
|
|
181
|
+
if (results.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: 'text', text: `No prompts found matching your search.\n\n${suffix}` }],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const lines = results.map((p) => `- ${p.isFavorite ? '⭐ ' : ''}${p.title} (id: \`${p.id}\`)${p.folderName ? ` — 📁 ${p.folderName}` : ''}`);
|
|
187
|
+
return {
|
|
188
|
+
content: [{
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: `Found ${results.length} prompt${results.length === 1 ? '' : 's'}:\n\n${lines.join('\n')}\n\n${suffix}`,
|
|
191
|
+
}],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
196
|
+
return errorResult(`Failed to search prompts: ${message}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// ── update_prompt ───────────────────────────────────────────────────────────
|
|
200
|
+
server.tool('update_prompt', 'Update the title and/or content of an existing prompt. If content changes, a new version is automatically created.', {
|
|
201
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
202
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
203
|
+
title: z.string().optional().describe('New title for the prompt'),
|
|
204
|
+
content: z.string().optional().describe('New content for the prompt'),
|
|
205
|
+
}, async ({ promptId, promptTitle, title, content }) => {
|
|
206
|
+
try {
|
|
207
|
+
if (!title && !content)
|
|
208
|
+
return errorResult('Provide at least a new title or content to update.');
|
|
209
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
210
|
+
if ('error' in resolved)
|
|
211
|
+
return errorResult(resolved.error);
|
|
212
|
+
const [result, suffix] = await Promise.all([
|
|
213
|
+
client.updatePrompt(resolved.id, { title, content }),
|
|
214
|
+
getResponseSuffix(),
|
|
215
|
+
]);
|
|
216
|
+
let text = `Prompt updated successfully!\nID: ${result.id}`;
|
|
217
|
+
if (result.versionCreated) {
|
|
218
|
+
text += `\nNew version created: v${result.newVersionNumber}`;
|
|
219
|
+
}
|
|
220
|
+
text += `\nURL: ${baseUrl}/workspace/prompt/${result.id}`;
|
|
221
|
+
text += `\n\n${suffix}`;
|
|
222
|
+
return { content: [{ type: 'text', text }] };
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
226
|
+
return errorResult(`Failed to update prompt: ${message}`);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// ── delete_prompt ───────────────────────────────────────────────────────────
|
|
230
|
+
server.tool('delete_prompt', 'Permanently delete a prompt from PromptingBox. This also deletes all versions and tag associations.', {
|
|
231
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
232
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
233
|
+
}, async ({ promptId, promptTitle }) => {
|
|
234
|
+
try {
|
|
235
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
236
|
+
if ('error' in resolved)
|
|
237
|
+
return errorResult(resolved.error);
|
|
238
|
+
await client.deletePrompt(resolved.id);
|
|
239
|
+
const suffix = await getResponseSuffix();
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: 'text', text: `Prompt deleted successfully.\n\n${suffix}` }],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
246
|
+
return errorResult(`Failed to delete prompt: ${message}`);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// ── duplicate_prompt ────────────────────────────────────────────────────────
|
|
250
|
+
server.tool('duplicate_prompt', 'Create a copy of an existing prompt. The copy gets "(Copy)" appended to the title and inherits the same folder and tags.', {
|
|
251
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
252
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
253
|
+
}, async ({ promptId, promptTitle }) => {
|
|
254
|
+
try {
|
|
255
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
256
|
+
if ('error' in resolved)
|
|
257
|
+
return errorResult(resolved.error);
|
|
258
|
+
const [result, suffix] = await Promise.all([
|
|
259
|
+
client.duplicatePrompt(resolved.id),
|
|
260
|
+
getResponseSuffix(),
|
|
261
|
+
]);
|
|
39
262
|
return {
|
|
40
|
-
content: [{
|
|
41
|
-
|
|
263
|
+
content: [{
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: `Prompt duplicated!\n\nTitle: ${result.title}\nID: ${result.id}\nURL: ${result.url}\n\n${suffix}`,
|
|
266
|
+
}],
|
|
42
267
|
};
|
|
43
268
|
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
271
|
+
return errorResult(`Failed to duplicate prompt: ${message}`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// ── toggle_favorite ─────────────────────────────────────────────────────────
|
|
275
|
+
server.tool('toggle_favorite', 'Star or unstar a prompt in PromptingBox.', {
|
|
276
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
277
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
278
|
+
isFavorite: z.boolean().describe('true to favorite, false to unfavorite'),
|
|
279
|
+
}, async ({ promptId, promptTitle, isFavorite }) => {
|
|
280
|
+
try {
|
|
281
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
282
|
+
if ('error' in resolved)
|
|
283
|
+
return errorResult(resolved.error);
|
|
284
|
+
await client.toggleFavorite(resolved.id, isFavorite);
|
|
285
|
+
const suffix = await getResponseSuffix();
|
|
286
|
+
const action = isFavorite ? 'favorited ⭐' : 'unfavorited';
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: 'text', text: `Prompt ${action}.\n\n${suffix}` }],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
293
|
+
return errorResult(`Failed to toggle favorite: ${message}`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
// ── add_tags ────────────────────────────────────────────────────────────────
|
|
297
|
+
server.tool('add_tags', 'Set tags on a prompt (by tag name). This replaces all existing tags on the prompt. Tags are auto-created if they don\'t exist.', {
|
|
298
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
299
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
300
|
+
tagNames: z.array(z.string()).describe('Tag names to set on the prompt (replaces existing tags)'),
|
|
301
|
+
}, async ({ promptId, promptTitle, tagNames }) => {
|
|
302
|
+
try {
|
|
303
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
304
|
+
if ('error' in resolved)
|
|
305
|
+
return errorResult(resolved.error);
|
|
306
|
+
const [result, suffix] = await Promise.all([
|
|
307
|
+
client.updatePromptTags(resolved.id, tagNames),
|
|
308
|
+
getResponseSuffix(),
|
|
309
|
+
]);
|
|
310
|
+
const tagList = result.tags.map((t) => t.name).join(', ');
|
|
311
|
+
return {
|
|
312
|
+
content: [{
|
|
313
|
+
type: 'text',
|
|
314
|
+
text: `Tags updated: ${tagList || '(no tags)'}.\n\n${suffix}`,
|
|
315
|
+
}],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
320
|
+
return errorResult(`Failed to update tags: ${message}`);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// ── delete_tag ──────────────────────────────────────────────────────────────
|
|
324
|
+
server.tool('delete_tag', 'Delete a tag entirely from PromptingBox. Removes it from all prompts that use it.', {
|
|
325
|
+
tagId: z.string().optional().describe('The tag ID. Provide this or tagName.'),
|
|
326
|
+
tagName: z.string().optional().describe('The tag name. Provide this or tagId.'),
|
|
327
|
+
}, async ({ tagId, tagName }) => {
|
|
328
|
+
try {
|
|
329
|
+
let resolvedId = tagId;
|
|
330
|
+
if (!resolvedId) {
|
|
331
|
+
if (!tagName)
|
|
332
|
+
return errorResult('Provide either tagId or tagName.');
|
|
333
|
+
const allTags = await client.listTags();
|
|
334
|
+
const lower = tagName.toLowerCase();
|
|
335
|
+
const matches = allTags.filter((t) => t.name.toLowerCase() === lower);
|
|
336
|
+
if (matches.length === 0)
|
|
337
|
+
return errorResult(`No tag found matching "${tagName}".`);
|
|
338
|
+
resolvedId = matches[0].id;
|
|
339
|
+
}
|
|
340
|
+
const [result, suffix] = await Promise.all([
|
|
341
|
+
client.deleteTag(resolvedId),
|
|
342
|
+
getResponseSuffix(),
|
|
343
|
+
]);
|
|
344
|
+
return {
|
|
345
|
+
content: [{ type: 'text', text: `Tag "${result.tagName}" deleted.\n\n${suffix}` }],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
350
|
+
return errorResult(`Failed to delete tag: ${message}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
// ── create_folder ───────────────────────────────────────────────────────────
|
|
354
|
+
server.tool('create_folder', 'Create a new folder in PromptingBox. If a folder with the same name exists, returns the existing one.', {
|
|
355
|
+
name: z.string().describe('The folder name to create'),
|
|
356
|
+
}, async ({ name }) => {
|
|
357
|
+
try {
|
|
358
|
+
const [result, suffix] = await Promise.all([
|
|
359
|
+
client.createFolder(name),
|
|
360
|
+
getResponseSuffix(),
|
|
361
|
+
]);
|
|
362
|
+
const status = result.alreadyExisted ? 'already exists' : 'created';
|
|
363
|
+
return {
|
|
364
|
+
content: [{
|
|
365
|
+
type: 'text',
|
|
366
|
+
text: `Folder "${result.name}" ${status}.\nID: ${result.id}\n\n${suffix}`,
|
|
367
|
+
}],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
372
|
+
return errorResult(`Failed to create folder: ${message}`);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
// ── delete_folder ───────────────────────────────────────────────────────────
|
|
376
|
+
server.tool('delete_folder', 'Delete a folder from PromptingBox. Prompts in the folder are moved to the root (not deleted).', {
|
|
377
|
+
folderId: z.string().optional().describe('The folder ID. Provide this or folderName.'),
|
|
378
|
+
folderName: z.string().optional().describe('The folder name. Provide this or folderId.'),
|
|
379
|
+
}, async ({ folderId, folderName }) => {
|
|
380
|
+
try {
|
|
381
|
+
let resolvedId = folderId;
|
|
382
|
+
if (!resolvedId) {
|
|
383
|
+
if (!folderName)
|
|
384
|
+
return errorResult('Provide either folderId or folderName.');
|
|
385
|
+
const allFolders = await client.listFolders();
|
|
386
|
+
const lower = folderName.toLowerCase();
|
|
387
|
+
const matches = allFolders.filter((f) => f.name.toLowerCase() === lower);
|
|
388
|
+
if (matches.length === 0)
|
|
389
|
+
return errorResult(`No folder found matching "${folderName}".`);
|
|
390
|
+
resolvedId = matches[0].id;
|
|
391
|
+
}
|
|
392
|
+
const [result, suffix] = await Promise.all([
|
|
393
|
+
client.deleteFolder(resolvedId),
|
|
394
|
+
getResponseSuffix(),
|
|
395
|
+
]);
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: 'text',
|
|
399
|
+
text: `Folder "${result.folderName}" deleted. Prompts moved to root.\n\n${suffix}`,
|
|
400
|
+
}],
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
405
|
+
return errorResult(`Failed to delete folder: ${message}`);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
// ── list_versions ───────────────────────────────────────────────────────────
|
|
409
|
+
server.tool('list_versions', 'Get the version history for a prompt. Shows all saved versions with their version numbers, notes, and timestamps.', {
|
|
410
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
411
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
412
|
+
}, async ({ promptId, promptTitle }) => {
|
|
413
|
+
try {
|
|
414
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
415
|
+
if ('error' in resolved)
|
|
416
|
+
return errorResult(resolved.error);
|
|
417
|
+
const [versions, suffix] = await Promise.all([
|
|
418
|
+
client.listVersions(resolved.id),
|
|
419
|
+
getResponseSuffix(),
|
|
420
|
+
]);
|
|
421
|
+
if (versions.length === 0) {
|
|
422
|
+
return {
|
|
423
|
+
content: [{ type: 'text', text: `No versions found.\n\n${suffix}` }],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const lines = versions.map((v) => `- **v${v.versionNumber}** — ${v.versionNote ?? 'No note'} (${new Date(v.createdAt).toLocaleDateString()})`);
|
|
427
|
+
return {
|
|
428
|
+
content: [{
|
|
429
|
+
type: 'text',
|
|
430
|
+
text: `Version history (${versions.length} version${versions.length === 1 ? '' : 's'}):\n\n${lines.join('\n')}\n\n${suffix}`,
|
|
431
|
+
}],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
436
|
+
return errorResult(`Failed to list versions: ${message}`);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
// ── restore_version ─────────────────────────────────────────────────────────
|
|
440
|
+
server.tool('restore_version', 'Restore a prompt to a previous version. Creates a new version with the restored content.', {
|
|
441
|
+
promptId: z.string().optional().describe('The prompt ID. Provide this or promptTitle.'),
|
|
442
|
+
promptTitle: z.string().optional().describe('The prompt title to search for. Provide this or promptId.'),
|
|
443
|
+
versionNumber: z.number().int().positive().describe('The version number to restore to'),
|
|
444
|
+
}, async ({ promptId, promptTitle, versionNumber }) => {
|
|
445
|
+
try {
|
|
446
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
447
|
+
if ('error' in resolved)
|
|
448
|
+
return errorResult(resolved.error);
|
|
449
|
+
const [result, suffix] = await Promise.all([
|
|
450
|
+
client.restoreVersion(resolved.id, versionNumber),
|
|
451
|
+
getResponseSuffix(),
|
|
452
|
+
]);
|
|
453
|
+
return {
|
|
454
|
+
content: [{
|
|
455
|
+
type: 'text',
|
|
456
|
+
text: `Restored to version ${result.restoredVersion}. New version created: v${result.newVersionNumber}.\n\n${suffix}`,
|
|
457
|
+
}],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
462
|
+
return errorResult(`Failed to restore version: ${message}`);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
// ── search_templates ────────────────────────────────────────────────────────
|
|
466
|
+
server.tool('search_templates', 'Browse and search the PromptingBox public template library. Find pre-built prompts you can save to your collection.', {
|
|
467
|
+
query: z.string().optional().describe('Search text to match against template titles and descriptions'),
|
|
468
|
+
category: z.string().optional().describe('Filter by category (e.g. "Business", "Writing", "Development")'),
|
|
469
|
+
limit: z.number().int().min(1).max(50).optional().default(10).describe('Number of results to return (default 10)'),
|
|
470
|
+
}, async ({ query, category, limit }) => {
|
|
471
|
+
try {
|
|
472
|
+
const result = await client.searchTemplates({ search: query, category, limit });
|
|
473
|
+
const suffix = await getResponseSuffix();
|
|
474
|
+
if (result.templates.length === 0) {
|
|
475
|
+
return {
|
|
476
|
+
content: [{ type: 'text', text: `No templates found matching your search.\n\n${suffix}` }],
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const lines = result.templates.map((t) => `- **${t.title}** (${t.category})${t.description ? ` — ${t.description}` : ''}\n ID: \`${t.id}\` | Used ${t.usageCount} times`);
|
|
480
|
+
return {
|
|
481
|
+
content: [{
|
|
482
|
+
type: 'text',
|
|
483
|
+
text: `Found ${result.pagination.total} template${result.pagination.total === 1 ? '' : 's'}` +
|
|
484
|
+
`${result.pagination.hasMore ? ` (showing first ${result.templates.length})` : ''}:\n\n${lines.join('\n\n')}` +
|
|
485
|
+
`\n\nUse \`use_template\` with the template ID to save one to your collection.\n\n${suffix}`,
|
|
486
|
+
}],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
catch (err) {
|
|
490
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
491
|
+
return errorResult(`Failed to search templates: ${message}`);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
// ── use_template ────────────────────────────────────────────────────────────
|
|
495
|
+
server.tool('use_template', 'Save a public template to your PromptingBox collection. Creates a copy you can edit and customize.', {
|
|
496
|
+
templateId: z.string().describe('The template ID (from search_templates)'),
|
|
497
|
+
}, async ({ templateId }) => {
|
|
498
|
+
try {
|
|
499
|
+
const [result, suffix] = await Promise.all([
|
|
500
|
+
client.useTemplate(templateId),
|
|
501
|
+
getResponseSuffix(),
|
|
502
|
+
]);
|
|
503
|
+
return {
|
|
504
|
+
content: [{
|
|
505
|
+
type: 'text',
|
|
506
|
+
text: `Template saved to your collection!\n\nTitle: ${result.title}\nID: ${result.promptId}\nURL: ${result.url}\n\n${suffix}`,
|
|
507
|
+
}],
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
512
|
+
return errorResult(`Failed to use template: ${message}`);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
// ── whoami ──────────────────────────────────────────────────────────────────
|
|
516
|
+
server.tool('whoami', 'Show which PromptingBox account is connected to this MCP server.', {}, async () => {
|
|
517
|
+
try {
|
|
518
|
+
const [info, update] = await Promise.all([
|
|
519
|
+
client.getAccountInfo(),
|
|
520
|
+
checkForUpdate(),
|
|
521
|
+
]);
|
|
522
|
+
accountEmail = info.email; // refresh cache
|
|
523
|
+
let text = `Connected to PromptingBox as:\n\nEmail: ${info.email}\nName: ${info.name || '(not set)'}\nID: ${info.id}`;
|
|
524
|
+
if (update)
|
|
525
|
+
text += `\n\n${update}`;
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: 'text', text }],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
532
|
+
return errorResult(`Failed to get account info: ${message}`);
|
|
533
|
+
}
|
|
44
534
|
});
|
|
45
535
|
// ── list_folders ─────────────────────────────────────────────────────────────
|
|
46
536
|
server.tool('list_folders', 'List all folders in the user\'s PromptingBox account. Useful to know where to save a prompt.', {}, async () => {
|
|
47
537
|
try {
|
|
48
|
-
const folders = await client.listFolders();
|
|
538
|
+
const [folders, suffix] = await Promise.all([client.listFolders(), getResponseSuffix()]);
|
|
49
539
|
if (folders.length === 0) {
|
|
50
540
|
return {
|
|
51
|
-
content: [{ type: 'text', text:
|
|
541
|
+
content: [{ type: 'text', text: `No folders found. You can specify a folder name when saving and it will be created automatically.\n\n${suffix}` }],
|
|
52
542
|
};
|
|
53
543
|
}
|
|
54
|
-
const list = folders.map((f) => `- ${f.name}`).join('\n');
|
|
544
|
+
const list = folders.map((f) => `- ${f.name} (id: \`${f.id}\`)`).join('\n');
|
|
55
545
|
return {
|
|
56
|
-
content: [{ type: 'text', text: `Folders in PromptingBox:\n${list}` }],
|
|
546
|
+
content: [{ type: 'text', text: `Folders in PromptingBox:\n${list}\n\n${suffix}` }],
|
|
57
547
|
};
|
|
58
548
|
}
|
|
59
549
|
catch (err) {
|
|
60
550
|
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
|
-
};
|
|
551
|
+
return errorResult(`Failed to list folders: ${message}`);
|
|
65
552
|
}
|
|
66
553
|
});
|
|
67
554
|
// ── list_prompts ─────────────────────────────────────────────────────────────
|
|
68
555
|
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
556
|
try {
|
|
70
|
-
const prompts = await client.listPrompts();
|
|
557
|
+
const [prompts, suffix] = await Promise.all([client.listPrompts(), getResponseSuffix()]);
|
|
71
558
|
if (prompts.length === 0) {
|
|
72
559
|
return {
|
|
73
|
-
content: [{ type: 'text', text:
|
|
560
|
+
content: [{ type: 'text', text: `No prompts found.\n\n${suffix}` }],
|
|
74
561
|
};
|
|
75
562
|
}
|
|
76
563
|
// Group by folder
|
|
@@ -89,25 +576,23 @@ server.tool('list_prompts', 'List all prompts in the user\'s PromptingBox accoun
|
|
|
89
576
|
return -1;
|
|
90
577
|
return a.localeCompare(b);
|
|
91
578
|
});
|
|
92
|
-
const baseUrl = BASE_URL ?? 'https://www.promptingbox.com';
|
|
93
579
|
const lines = [`Your PromptingBox prompts (${prompts.length} total):\n`];
|
|
94
580
|
for (const key of sortedKeys) {
|
|
95
581
|
lines.push(`📁 ${key}`);
|
|
96
582
|
for (const p of grouped.get(key)) {
|
|
97
|
-
|
|
583
|
+
const fav = p.isFavorite ? '⭐ ' : '';
|
|
584
|
+
lines.push(` • ${fav}[${p.title}](${baseUrl}/workspace/prompt/${p.id}) \`${p.id}\``);
|
|
98
585
|
}
|
|
99
586
|
lines.push('');
|
|
100
587
|
}
|
|
588
|
+
lines.push(suffix);
|
|
101
589
|
return {
|
|
102
590
|
content: [{ type: 'text', text: lines.join('\n').trimEnd() }],
|
|
103
591
|
};
|
|
104
592
|
}
|
|
105
593
|
catch (err) {
|
|
106
594
|
const message = err instanceof Error ? err.message : String(err);
|
|
107
|
-
return {
|
|
108
|
-
content: [{ type: 'text', text: `Failed to list prompts: ${message}` }],
|
|
109
|
-
isError: true,
|
|
110
|
-
};
|
|
595
|
+
return errorResult(`Failed to list prompts: ${message}`);
|
|
111
596
|
}
|
|
112
597
|
});
|
|
113
598
|
// ── move_prompt_to_folder ─────────────────────────────────────────────────
|
|
@@ -117,65 +602,37 @@ server.tool('move_prompt_to_folder', 'Move a prompt to a different folder. Provi
|
|
|
117
602
|
folder: z.string().describe('The folder name to move the prompt into'),
|
|
118
603
|
}, async ({ promptId, promptTitle, folder }) => {
|
|
119
604
|
try {
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
isError: true,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
const all = await client.listPrompts();
|
|
129
|
-
const lower = promptTitle.toLowerCase();
|
|
130
|
-
const matches = all.filter((p) => p.title.toLowerCase().includes(lower));
|
|
131
|
-
if (matches.length === 0) {
|
|
132
|
-
return {
|
|
133
|
-
content: [{ type: 'text', text: `No prompt found matching "${promptTitle}".` }],
|
|
134
|
-
isError: true,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (matches.length > 1) {
|
|
138
|
-
const list = matches.map((p) => `- ${p.title} (id: ${p.id})`).join('\n');
|
|
139
|
-
return {
|
|
140
|
-
content: [{ type: 'text', text: `Multiple prompts match "${promptTitle}". Use promptId to be specific:\n${list}` }],
|
|
141
|
-
isError: true,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
resolvedId = matches[0].id;
|
|
145
|
-
}
|
|
146
|
-
await client.movePromptToFolder(resolvedId, folder);
|
|
605
|
+
const resolved = await resolvePromptId(promptId, promptTitle);
|
|
606
|
+
if ('error' in resolved)
|
|
607
|
+
return errorResult(resolved.error);
|
|
608
|
+
await client.movePromptToFolder(resolved.id, folder);
|
|
609
|
+
const suffix = await getResponseSuffix();
|
|
147
610
|
return {
|
|
148
|
-
content: [{ type: 'text', text: `Moved prompt to folder "${folder}"
|
|
611
|
+
content: [{ type: 'text', text: `Moved prompt to folder "${folder}".\n\n${suffix}` }],
|
|
149
612
|
};
|
|
150
613
|
}
|
|
151
614
|
catch (err) {
|
|
152
615
|
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
-
return {
|
|
154
|
-
content: [{ type: 'text', text: `Failed to move prompt: ${message}` }],
|
|
155
|
-
isError: true,
|
|
156
|
-
};
|
|
616
|
+
return errorResult(`Failed to move prompt: ${message}`);
|
|
157
617
|
}
|
|
158
618
|
});
|
|
159
619
|
// ── list_tags ────────────────────────────────────────────────────────────────
|
|
160
620
|
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 () => {
|
|
161
621
|
try {
|
|
162
|
-
const tags = await client.listTags();
|
|
622
|
+
const [tags, suffix] = await Promise.all([client.listTags(), getResponseSuffix()]);
|
|
163
623
|
if (tags.length === 0) {
|
|
164
624
|
return {
|
|
165
|
-
content: [{ type: 'text', text:
|
|
625
|
+
content: [{ type: 'text', text: `No tags found. You can specify tag names when saving and they will be created automatically.\n\n${suffix}` }],
|
|
166
626
|
};
|
|
167
627
|
}
|
|
168
|
-
const list = tags.map((t) => `- ${t.name}`).join('\n');
|
|
628
|
+
const list = tags.map((t) => `- ${t.name} (id: \`${t.id}\`)`).join('\n');
|
|
169
629
|
return {
|
|
170
|
-
content: [{ type: 'text', text: `Tags in PromptingBox:\n${list}` }],
|
|
630
|
+
content: [{ type: 'text', text: `Tags in PromptingBox:\n${list}\n\n${suffix}` }],
|
|
171
631
|
};
|
|
172
632
|
}
|
|
173
633
|
catch (err) {
|
|
174
634
|
const message = err instanceof Error ? err.message : String(err);
|
|
175
|
-
return {
|
|
176
|
-
content: [{ type: 'text', text: `Failed to list tags: ${message}` }],
|
|
177
|
-
isError: true,
|
|
178
|
-
};
|
|
635
|
+
return errorResult(`Failed to list tags: ${message}`);
|
|
179
636
|
}
|
|
180
637
|
});
|
|
181
638
|
// ── start server ─────────────────────────────────────────────────────────────
|