pulsemcp-cms-admin-mcp-server 0.6.3 → 0.6.4
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 +16 -6
- package/build/shared/src/pulsemcp-admin-client/lib/create-redirect.js +41 -0
- package/build/shared/src/pulsemcp-admin-client/lib/delete-redirect.js +24 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-redirect.js +31 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-redirects.js +52 -0
- package/build/shared/src/pulsemcp-admin-client/lib/update-redirect.js +47 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +37 -0
- package/build/shared/src/server.js +21 -0
- package/build/shared/src/tools/create-redirect.js +76 -0
- package/build/shared/src/tools/delete-redirect.js +51 -0
- package/build/shared/src/tools/get-redirect.js +65 -0
- package/build/shared/src/tools/get-redirects.js +91 -0
- package/build/shared/src/tools/update-redirect.js +97 -0
- package/build/shared/src/tools.js +17 -0
- package/package.json +1 -1
- package/shared/pulsemcp-admin-client/lib/create-redirect.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/create-redirect.js +41 -0
- package/shared/pulsemcp-admin-client/lib/delete-redirect.d.ts +5 -0
- package/shared/pulsemcp-admin-client/lib/delete-redirect.js +24 -0
- package/shared/pulsemcp-admin-client/lib/get-redirect.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/get-redirect.js +31 -0
- package/shared/pulsemcp-admin-client/lib/get-redirects.d.ts +8 -0
- package/shared/pulsemcp-admin-client/lib/get-redirects.js +52 -0
- package/shared/pulsemcp-admin-client/lib/update-redirect.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/update-redirect.js +47 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +37 -0
- package/shared/server.d.ts +27 -1
- package/shared/server.js +21 -0
- package/shared/tools/create-redirect.d.ts +39 -0
- package/shared/tools/create-redirect.js +76 -0
- package/shared/tools/delete-redirect.d.ts +30 -0
- package/shared/tools/delete-redirect.js +51 -0
- package/shared/tools/get-redirect.d.ts +30 -0
- package/shared/tools/get-redirect.js +65 -0
- package/shared/tools/get-redirects.d.ts +45 -0
- package/shared/tools/get-redirects.js +91 -0
- package/shared/tools/update-redirect.d.ts +43 -0
- package/shared/tools/update-redirect.js +97 -0
- package/shared/tools.d.ts +4 -1
- package/shared/tools.js +17 -0
- package/shared/types.d.ts +29 -0
package/README.md
CHANGED
|
@@ -77,6 +77,11 @@ This server is built and tested on macOS with Claude Desktop. It should work wit
|
|
|
77
77
|
| `list_mcp_servers` | mcp_servers | read | List/search MCP servers with filtering by status, classification, pagination. |
|
|
78
78
|
| `get_mcp_server` | mcp_servers | read | Get detailed MCP server info by slug (unified view of all admin UI fields). |
|
|
79
79
|
| `update_mcp_server` | mcp_servers | write | Update an MCP server's fields (all admin UI fields supported). |
|
|
80
|
+
| `get_redirects` | redirects | read | List URL redirects with search, status filtering, and pagination. |
|
|
81
|
+
| `get_redirect` | redirects | read | Get detailed redirect info by ID. |
|
|
82
|
+
| `create_redirect` | redirects | write | Create a new URL redirect entry. |
|
|
83
|
+
| `update_redirect` | redirects | write | Update an existing URL redirect by ID. |
|
|
84
|
+
| `delete_redirect` | redirects | write | Delete a URL redirect by ID (irreversible). |
|
|
80
85
|
|
|
81
86
|
# Tool Groups
|
|
82
87
|
|
|
@@ -105,6 +110,8 @@ This server organizes tools into groups that can be selectively enabled or disab
|
|
|
105
110
|
| `mcp_jsons_readonly` | 2 | MCP JSON configurations read-only |
|
|
106
111
|
| `mcp_servers` | 3 | Full MCP servers management (read + write) |
|
|
107
112
|
| `mcp_servers_readonly` | 2 | MCP servers read-only (list, get) |
|
|
113
|
+
| `redirects` | 5 | Full URL redirect management (read + write) |
|
|
114
|
+
| `redirects_readonly` | 2 | URL redirects read-only (list, get) |
|
|
108
115
|
|
|
109
116
|
### Tools by Group
|
|
110
117
|
|
|
@@ -130,12 +137,15 @@ This server organizes tools into groups that can be selectively enabled or disab
|
|
|
130
137
|
- **mcp_servers** / **mcp_servers_readonly**:
|
|
131
138
|
- Read-only: `list_mcp_servers`, `get_mcp_server`
|
|
132
139
|
- Write: `update_mcp_server`
|
|
140
|
+
- **redirects** / **redirects_readonly**:
|
|
141
|
+
- Read-only: `get_redirects`, `get_redirect`
|
|
142
|
+
- Write: `create_redirect`, `update_redirect`, `delete_redirect`
|
|
133
143
|
|
|
134
144
|
## Environment Variables
|
|
135
145
|
|
|
136
|
-
| Variable | Description | Default
|
|
137
|
-
| ------------- | ------------------------------------------- |
|
|
138
|
-
| `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `newsletter,server_queue,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers` (all base groups) |
|
|
146
|
+
| Variable | Description | Default |
|
|
147
|
+
| ------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `newsletter,server_queue,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects` (all base groups) |
|
|
139
149
|
|
|
140
150
|
## Examples
|
|
141
151
|
|
|
@@ -160,7 +170,7 @@ TOOL_GROUPS=server_queue_readonly
|
|
|
160
170
|
Enable all groups with read-only access:
|
|
161
171
|
|
|
162
172
|
```bash
|
|
163
|
-
TOOL_GROUPS=newsletter_readonly,server_queue_readonly,official_queue_readonly,unofficial_mirrors_readonly,official_mirrors_readonly,tenants_readonly,mcp_jsons_readonly,mcp_servers_readonly
|
|
173
|
+
TOOL_GROUPS=newsletter_readonly,server_queue_readonly,official_queue_readonly,unofficial_mirrors_readonly,official_mirrors_readonly,tenants_readonly,mcp_jsons_readonly,mcp_servers_readonly,redirects_readonly
|
|
164
174
|
```
|
|
165
175
|
|
|
166
176
|
Mix full and read-only access per group:
|
|
@@ -296,7 +306,7 @@ Add to your Claude Desktop configuration:
|
|
|
296
306
|
"args": ["/path/to/pulsemcp-cms-admin/local/build/index.js"],
|
|
297
307
|
"env": {
|
|
298
308
|
"PULSEMCP_ADMIN_API_KEY": "your-api-key-here",
|
|
299
|
-
"TOOL_GROUPS": "newsletter,server_queue,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers"
|
|
309
|
+
"TOOL_GROUPS": "newsletter,server_queue,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers,redirects"
|
|
300
310
|
}
|
|
301
311
|
}
|
|
302
312
|
}
|
|
@@ -313,7 +323,7 @@ For read-only access:
|
|
|
313
323
|
"args": ["/path/to/pulsemcp-cms-admin/local/build/index.js"],
|
|
314
324
|
"env": {
|
|
315
325
|
"PULSEMCP_ADMIN_API_KEY": "your-api-key-here",
|
|
316
|
-
"TOOL_GROUPS": "newsletter_readonly,server_queue_readonly,official_queue_readonly,unofficial_mirrors_readonly,official_mirrors_readonly,tenants_readonly,mcp_jsons_readonly,mcp_servers_readonly"
|
|
326
|
+
"TOOL_GROUPS": "newsletter_readonly,server_queue_readonly,official_queue_readonly,unofficial_mirrors_readonly,official_mirrors_readonly,tenants_readonly,mcp_jsons_readonly,mcp_servers_readonly,redirects_readonly"
|
|
317
327
|
}
|
|
318
328
|
}
|
|
319
329
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export async function createRedirect(apiKey, baseUrl, params) {
|
|
2
|
+
const url = new URL('/api/redirects', baseUrl);
|
|
3
|
+
const body = {
|
|
4
|
+
from: params.from,
|
|
5
|
+
to: params.to,
|
|
6
|
+
};
|
|
7
|
+
if (params.status !== undefined) {
|
|
8
|
+
body.status = params.status;
|
|
9
|
+
}
|
|
10
|
+
const response = await fetch(url.toString(), {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: {
|
|
13
|
+
'X-API-Key': apiKey,
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
Accept: 'application/json',
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify(body),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
if (response.status === 401) {
|
|
21
|
+
throw new Error('Invalid API key');
|
|
22
|
+
}
|
|
23
|
+
if (response.status === 403) {
|
|
24
|
+
throw new Error('User lacks write privileges');
|
|
25
|
+
}
|
|
26
|
+
if (response.status === 422) {
|
|
27
|
+
const errorData = (await response.json());
|
|
28
|
+
throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Failed to create redirect: ${response.status} ${response.statusText}`);
|
|
31
|
+
}
|
|
32
|
+
const redirect = (await response.json());
|
|
33
|
+
return {
|
|
34
|
+
id: redirect.id,
|
|
35
|
+
from: redirect.from,
|
|
36
|
+
to: redirect.to,
|
|
37
|
+
status: redirect.status,
|
|
38
|
+
created_at: redirect.created_at,
|
|
39
|
+
updated_at: redirect.updated_at,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function deleteRedirect(apiKey, baseUrl, id) {
|
|
2
|
+
const url = new URL(`/api/redirects/${id}`, baseUrl);
|
|
3
|
+
const response = await fetch(url.toString(), {
|
|
4
|
+
method: 'DELETE',
|
|
5
|
+
headers: {
|
|
6
|
+
'X-API-Key': apiKey,
|
|
7
|
+
Accept: 'application/json',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
if (response.status === 401) {
|
|
12
|
+
throw new Error('Invalid API key');
|
|
13
|
+
}
|
|
14
|
+
if (response.status === 403) {
|
|
15
|
+
throw new Error('User lacks write privileges');
|
|
16
|
+
}
|
|
17
|
+
if (response.status === 404) {
|
|
18
|
+
throw new Error(`Redirect with ID ${id} not found`);
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Failed to delete redirect: ${response.status} ${response.statusText}`);
|
|
21
|
+
}
|
|
22
|
+
const data = (await response.json());
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export async function getRedirect(apiKey, baseUrl, id) {
|
|
2
|
+
const url = new URL(`/api/redirects/${id}`, baseUrl);
|
|
3
|
+
const response = await fetch(url.toString(), {
|
|
4
|
+
method: 'GET',
|
|
5
|
+
headers: {
|
|
6
|
+
'X-API-Key': apiKey,
|
|
7
|
+
Accept: 'application/json',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
if (response.status === 401) {
|
|
12
|
+
throw new Error('Invalid API key');
|
|
13
|
+
}
|
|
14
|
+
if (response.status === 403) {
|
|
15
|
+
throw new Error('User lacks admin privileges');
|
|
16
|
+
}
|
|
17
|
+
if (response.status === 404) {
|
|
18
|
+
throw new Error(`Redirect with ID ${id} not found`);
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Failed to fetch redirect: ${response.status} ${response.statusText}`);
|
|
21
|
+
}
|
|
22
|
+
const redirect = (await response.json());
|
|
23
|
+
return {
|
|
24
|
+
id: redirect.id,
|
|
25
|
+
from: redirect.from,
|
|
26
|
+
to: redirect.to,
|
|
27
|
+
status: redirect.status,
|
|
28
|
+
created_at: redirect.created_at,
|
|
29
|
+
updated_at: redirect.updated_at,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function mapRedirect(redirect) {
|
|
2
|
+
return {
|
|
3
|
+
id: redirect.id,
|
|
4
|
+
from: redirect.from,
|
|
5
|
+
to: redirect.to,
|
|
6
|
+
status: redirect.status,
|
|
7
|
+
created_at: redirect.created_at,
|
|
8
|
+
updated_at: redirect.updated_at,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export async function getRedirects(apiKey, baseUrl, params) {
|
|
12
|
+
const url = new URL('/api/redirects', baseUrl);
|
|
13
|
+
if (params?.q) {
|
|
14
|
+
url.searchParams.append('q', params.q);
|
|
15
|
+
}
|
|
16
|
+
if (params?.status) {
|
|
17
|
+
url.searchParams.append('status', params.status);
|
|
18
|
+
}
|
|
19
|
+
if (params?.limit) {
|
|
20
|
+
url.searchParams.append('limit', params.limit.toString());
|
|
21
|
+
}
|
|
22
|
+
if (params?.offset) {
|
|
23
|
+
url.searchParams.append('offset', params.offset.toString());
|
|
24
|
+
}
|
|
25
|
+
const response = await fetch(url.toString(), {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
headers: {
|
|
28
|
+
'X-API-Key': apiKey,
|
|
29
|
+
Accept: 'application/json',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
if (response.status === 401) {
|
|
34
|
+
throw new Error('Invalid API key');
|
|
35
|
+
}
|
|
36
|
+
if (response.status === 403) {
|
|
37
|
+
throw new Error('User lacks admin privileges');
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Failed to fetch redirects: ${response.status} ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
const data = (await response.json());
|
|
42
|
+
return {
|
|
43
|
+
redirects: data.data.map(mapRedirect),
|
|
44
|
+
pagination: {
|
|
45
|
+
current_page: data.meta.current_page,
|
|
46
|
+
total_pages: data.meta.total_pages,
|
|
47
|
+
total_count: data.meta.total_count,
|
|
48
|
+
has_next: data.meta.has_next,
|
|
49
|
+
limit: data.meta.limit,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export async function updateRedirect(apiKey, baseUrl, id, params) {
|
|
2
|
+
const url = new URL(`/api/redirects/${id}`, baseUrl);
|
|
3
|
+
const body = {};
|
|
4
|
+
if (params.from !== undefined) {
|
|
5
|
+
body.from = params.from;
|
|
6
|
+
}
|
|
7
|
+
if (params.to !== undefined) {
|
|
8
|
+
body.to = params.to;
|
|
9
|
+
}
|
|
10
|
+
if (params.status !== undefined) {
|
|
11
|
+
body.status = params.status;
|
|
12
|
+
}
|
|
13
|
+
const response = await fetch(url.toString(), {
|
|
14
|
+
method: 'PUT',
|
|
15
|
+
headers: {
|
|
16
|
+
'X-API-Key': apiKey,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
Accept: 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify(body),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
if (response.status === 401) {
|
|
24
|
+
throw new Error('Invalid API key');
|
|
25
|
+
}
|
|
26
|
+
if (response.status === 403) {
|
|
27
|
+
throw new Error('User lacks write privileges');
|
|
28
|
+
}
|
|
29
|
+
if (response.status === 404) {
|
|
30
|
+
throw new Error(`Redirect with ID ${id} not found`);
|
|
31
|
+
}
|
|
32
|
+
if (response.status === 422) {
|
|
33
|
+
const errorData = (await response.json());
|
|
34
|
+
throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Failed to update redirect: ${response.status} ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const redirect = (await response.json());
|
|
39
|
+
return {
|
|
40
|
+
id: redirect.id,
|
|
41
|
+
from: redirect.from,
|
|
42
|
+
to: redirect.to,
|
|
43
|
+
status: redirect.status,
|
|
44
|
+
created_at: redirect.created_at,
|
|
45
|
+
updated_at: redirect.updated_at,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -811,5 +811,42 @@ export function createMockPulseMCPAdminClient(mockData) {
|
|
|
811
811
|
updated_at: new Date().toISOString(),
|
|
812
812
|
};
|
|
813
813
|
},
|
|
814
|
+
// Redirect REST API methods (stub implementations)
|
|
815
|
+
async getRedirects() {
|
|
816
|
+
return { redirects: [], pagination: { current_page: 1, total_pages: 1, total_count: 0 } };
|
|
817
|
+
},
|
|
818
|
+
async getRedirect(id) {
|
|
819
|
+
return {
|
|
820
|
+
id,
|
|
821
|
+
from: '/old-page',
|
|
822
|
+
to: '/new-page',
|
|
823
|
+
status: 'active',
|
|
824
|
+
created_at: new Date().toISOString(),
|
|
825
|
+
updated_at: new Date().toISOString(),
|
|
826
|
+
};
|
|
827
|
+
},
|
|
828
|
+
async createRedirect(params) {
|
|
829
|
+
return {
|
|
830
|
+
id: 1,
|
|
831
|
+
from: params.from,
|
|
832
|
+
to: params.to,
|
|
833
|
+
status: params.status || 'draft',
|
|
834
|
+
created_at: new Date().toISOString(),
|
|
835
|
+
updated_at: new Date().toISOString(),
|
|
836
|
+
};
|
|
837
|
+
},
|
|
838
|
+
async updateRedirect(id, params) {
|
|
839
|
+
return {
|
|
840
|
+
id,
|
|
841
|
+
from: params.from || '/old-page',
|
|
842
|
+
to: params.to || '/new-page',
|
|
843
|
+
status: params.status || 'active',
|
|
844
|
+
created_at: new Date().toISOString(),
|
|
845
|
+
updated_at: new Date().toISOString(),
|
|
846
|
+
};
|
|
847
|
+
},
|
|
848
|
+
async deleteRedirect() {
|
|
849
|
+
return { success: true, message: 'Redirect deleted' };
|
|
850
|
+
},
|
|
814
851
|
};
|
|
815
852
|
}
|
|
@@ -190,6 +190,27 @@ export class PulseMCPAdminClient {
|
|
|
190
190
|
const { updateUnifiedMCPServer } = await import('./pulsemcp-admin-client/lib/update-unified-mcp-server.js');
|
|
191
191
|
return updateUnifiedMCPServer(this.apiKey, this.baseUrl, implementationId, params);
|
|
192
192
|
}
|
|
193
|
+
// Redirect REST API methods
|
|
194
|
+
async getRedirects(params) {
|
|
195
|
+
const { getRedirects } = await import('./pulsemcp-admin-client/lib/get-redirects.js');
|
|
196
|
+
return getRedirects(this.apiKey, this.baseUrl, params);
|
|
197
|
+
}
|
|
198
|
+
async getRedirect(id) {
|
|
199
|
+
const { getRedirect } = await import('./pulsemcp-admin-client/lib/get-redirect.js');
|
|
200
|
+
return getRedirect(this.apiKey, this.baseUrl, id);
|
|
201
|
+
}
|
|
202
|
+
async createRedirect(params) {
|
|
203
|
+
const { createRedirect } = await import('./pulsemcp-admin-client/lib/create-redirect.js');
|
|
204
|
+
return createRedirect(this.apiKey, this.baseUrl, params);
|
|
205
|
+
}
|
|
206
|
+
async updateRedirect(id, params) {
|
|
207
|
+
const { updateRedirect } = await import('./pulsemcp-admin-client/lib/update-redirect.js');
|
|
208
|
+
return updateRedirect(this.apiKey, this.baseUrl, id, params);
|
|
209
|
+
}
|
|
210
|
+
async deleteRedirect(id) {
|
|
211
|
+
const { deleteRedirect } = await import('./pulsemcp-admin-client/lib/delete-redirect.js');
|
|
212
|
+
return deleteRedirect(this.apiKey, this.baseUrl, id);
|
|
213
|
+
}
|
|
193
214
|
}
|
|
194
215
|
export function createMCPServer(options) {
|
|
195
216
|
const server = new Server({
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
from: 'The source path to redirect from (e.g., "/old-page")',
|
|
4
|
+
to: 'The destination path or URL to redirect to (e.g., "/new-page" or "https://example.com")',
|
|
5
|
+
status: 'The status of the redirect (draft, active, paused, archived). Defaults to draft.',
|
|
6
|
+
};
|
|
7
|
+
const CreateRedirectSchema = z.object({
|
|
8
|
+
from: z.string().describe(PARAM_DESCRIPTIONS.from),
|
|
9
|
+
to: z.string().describe(PARAM_DESCRIPTIONS.to),
|
|
10
|
+
status: z
|
|
11
|
+
.enum(['draft', 'active', 'paused', 'archived'])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(PARAM_DESCRIPTIONS.status),
|
|
14
|
+
});
|
|
15
|
+
export function createRedirect(_server, clientFactory) {
|
|
16
|
+
return {
|
|
17
|
+
name: 'create_redirect',
|
|
18
|
+
description: `Create a new URL redirect entry. Redirects manage URL routing on the PulseMCP website.
|
|
19
|
+
|
|
20
|
+
Example request:
|
|
21
|
+
{
|
|
22
|
+
"from": "/old-page",
|
|
23
|
+
"to": "/new-page",
|
|
24
|
+
"status": "draft"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Use cases:
|
|
28
|
+
- Create a new redirect when moving content to a new URL
|
|
29
|
+
- Set up redirects for deprecated pages
|
|
30
|
+
- Create draft redirects for review before activation`,
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
from: { type: 'string', description: PARAM_DESCRIPTIONS.from },
|
|
35
|
+
to: { type: 'string', description: PARAM_DESCRIPTIONS.to },
|
|
36
|
+
status: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['draft', 'active', 'paused', 'archived'],
|
|
39
|
+
description: PARAM_DESCRIPTIONS.status,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['from', 'to'],
|
|
43
|
+
},
|
|
44
|
+
handler: async (args) => {
|
|
45
|
+
const validatedArgs = CreateRedirectSchema.parse(args);
|
|
46
|
+
const client = clientFactory();
|
|
47
|
+
try {
|
|
48
|
+
const redirect = await client.createRedirect({
|
|
49
|
+
from: validatedArgs.from,
|
|
50
|
+
to: validatedArgs.to,
|
|
51
|
+
status: validatedArgs.status,
|
|
52
|
+
});
|
|
53
|
+
let content = `Successfully created redirect!\n\n`;
|
|
54
|
+
content += `**ID:** ${redirect.id}\n`;
|
|
55
|
+
content += `**From:** ${redirect.from}\n`;
|
|
56
|
+
content += `**To:** ${redirect.to}\n`;
|
|
57
|
+
content += `**Status:** ${redirect.status}\n`;
|
|
58
|
+
if (redirect.created_at) {
|
|
59
|
+
content += `**Created:** ${redirect.created_at}\n`;
|
|
60
|
+
}
|
|
61
|
+
return { content: [{ type: 'text', text: content }] };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: `Error creating redirect: ${error instanceof Error ? error.message : String(error)}`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
id: 'The ID of the redirect to delete',
|
|
4
|
+
};
|
|
5
|
+
const DeleteRedirectSchema = z.object({
|
|
6
|
+
id: z.number().describe(PARAM_DESCRIPTIONS.id),
|
|
7
|
+
});
|
|
8
|
+
export function deleteRedirect(_server, clientFactory) {
|
|
9
|
+
return {
|
|
10
|
+
name: 'delete_redirect',
|
|
11
|
+
description: `Delete a URL redirect by its ID. This action is irreversible.
|
|
12
|
+
|
|
13
|
+
Use cases:
|
|
14
|
+
- Remove duplicate or outdated redirects
|
|
15
|
+
- Clean up test data
|
|
16
|
+
- Remove redirects that were incorrectly created`,
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
id: { type: 'number', description: PARAM_DESCRIPTIONS.id },
|
|
21
|
+
},
|
|
22
|
+
required: ['id'],
|
|
23
|
+
},
|
|
24
|
+
handler: async (args) => {
|
|
25
|
+
const validatedArgs = DeleteRedirectSchema.parse(args);
|
|
26
|
+
const client = clientFactory();
|
|
27
|
+
try {
|
|
28
|
+
const result = await client.deleteRedirect(validatedArgs.id);
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: 'text',
|
|
33
|
+
text: `Successfully deleted redirect (ID: ${validatedArgs.id}).\n\n${result.message}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'text',
|
|
43
|
+
text: `Error deleting redirect: ${error instanceof Error ? error.message : String(error)}`,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
id: 'The ID of the redirect to retrieve',
|
|
4
|
+
};
|
|
5
|
+
const GetRedirectSchema = z.object({
|
|
6
|
+
id: z.number().describe(PARAM_DESCRIPTIONS.id),
|
|
7
|
+
});
|
|
8
|
+
export function getRedirect(_server, clientFactory) {
|
|
9
|
+
return {
|
|
10
|
+
name: 'get_redirect',
|
|
11
|
+
description: `Retrieve a single URL redirect by its ID. Returns detailed information including the from/to paths and status.
|
|
12
|
+
|
|
13
|
+
Example response:
|
|
14
|
+
{
|
|
15
|
+
"id": 123,
|
|
16
|
+
"from": "/old-page",
|
|
17
|
+
"to": "/new-page",
|
|
18
|
+
"status": "active",
|
|
19
|
+
"created_at": "2024-01-15T10:00:00Z",
|
|
20
|
+
"updated_at": "2024-01-15T10:00:00Z"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Use cases:
|
|
24
|
+
- Get detailed information about a specific redirect
|
|
25
|
+
- Check the current state of a redirect before updating
|
|
26
|
+
- Verify redirect configuration`,
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
id: { type: 'number', description: PARAM_DESCRIPTIONS.id },
|
|
31
|
+
},
|
|
32
|
+
required: ['id'],
|
|
33
|
+
},
|
|
34
|
+
handler: async (args) => {
|
|
35
|
+
const validatedArgs = GetRedirectSchema.parse(args);
|
|
36
|
+
const client = clientFactory();
|
|
37
|
+
try {
|
|
38
|
+
const redirect = await client.getRedirect(validatedArgs.id);
|
|
39
|
+
let content = `**Redirect Details**\n\n`;
|
|
40
|
+
content += `**ID:** ${redirect.id}\n`;
|
|
41
|
+
content += `**From:** ${redirect.from}\n`;
|
|
42
|
+
content += `**To:** ${redirect.to}\n`;
|
|
43
|
+
content += `**Status:** ${redirect.status}\n`;
|
|
44
|
+
if (redirect.created_at) {
|
|
45
|
+
content += `**Created:** ${redirect.created_at}\n`;
|
|
46
|
+
}
|
|
47
|
+
if (redirect.updated_at) {
|
|
48
|
+
content += `**Updated:** ${redirect.updated_at}\n`;
|
|
49
|
+
}
|
|
50
|
+
return { content: [{ type: 'text', text: content }] };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Error fetching redirect: ${error instanceof Error ? error.message : String(error)}`,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
q: 'Search query to filter by from or to paths',
|
|
4
|
+
status: 'Filter by status (draft, active, paused, archived)',
|
|
5
|
+
limit: 'Results per page, range 1-100. Default: 30',
|
|
6
|
+
offset: 'Pagination offset. Default: 0',
|
|
7
|
+
};
|
|
8
|
+
const GetRedirectsSchema = z.object({
|
|
9
|
+
q: z.string().optional().describe(PARAM_DESCRIPTIONS.q),
|
|
10
|
+
status: z
|
|
11
|
+
.enum(['draft', 'active', 'paused', 'archived'])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe(PARAM_DESCRIPTIONS.status),
|
|
14
|
+
limit: z.number().min(1).max(100).optional().describe(PARAM_DESCRIPTIONS.limit),
|
|
15
|
+
offset: z.number().min(0).optional().describe(PARAM_DESCRIPTIONS.offset),
|
|
16
|
+
});
|
|
17
|
+
export function getRedirects(_server, clientFactory) {
|
|
18
|
+
return {
|
|
19
|
+
name: 'get_redirects',
|
|
20
|
+
description: `Retrieve a paginated list of URL redirects from the PulseMCP Admin API. Redirects manage URL routing on the PulseMCP website.
|
|
21
|
+
|
|
22
|
+
Example response:
|
|
23
|
+
{
|
|
24
|
+
"redirects": [
|
|
25
|
+
{
|
|
26
|
+
"id": 123,
|
|
27
|
+
"from": "/old-page",
|
|
28
|
+
"to": "/new-page",
|
|
29
|
+
"status": "active"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"pagination": { "current_page": 1, "total_pages": 5, "total_count": 150 }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Use cases:
|
|
36
|
+
- Browse redirects to find existing URL mappings
|
|
37
|
+
- Search for specific redirects by path
|
|
38
|
+
- Filter redirects by status (draft, active, paused, archived)
|
|
39
|
+
- Review redirects before activating or modifying them`,
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
q: { type: 'string', description: PARAM_DESCRIPTIONS.q },
|
|
44
|
+
status: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: ['draft', 'active', 'paused', 'archived'],
|
|
47
|
+
description: PARAM_DESCRIPTIONS.status,
|
|
48
|
+
},
|
|
49
|
+
limit: { type: 'number', minimum: 1, maximum: 100, description: PARAM_DESCRIPTIONS.limit },
|
|
50
|
+
offset: { type: 'number', minimum: 0, description: PARAM_DESCRIPTIONS.offset },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
handler: async (args) => {
|
|
54
|
+
const validatedArgs = GetRedirectsSchema.parse(args);
|
|
55
|
+
const client = clientFactory();
|
|
56
|
+
try {
|
|
57
|
+
const response = await client.getRedirects({
|
|
58
|
+
q: validatedArgs.q,
|
|
59
|
+
status: validatedArgs.status,
|
|
60
|
+
limit: validatedArgs.limit,
|
|
61
|
+
offset: validatedArgs.offset,
|
|
62
|
+
});
|
|
63
|
+
let content = `Found ${response.redirects.length} redirects`;
|
|
64
|
+
if (response.pagination) {
|
|
65
|
+
content += ` (page ${response.pagination.current_page} of ${response.pagination.total_pages}, total: ${response.pagination.total_count})`;
|
|
66
|
+
}
|
|
67
|
+
content += ':\n\n';
|
|
68
|
+
for (const [index, redirect] of response.redirects.entries()) {
|
|
69
|
+
content += `${index + 1}. **${redirect.from}** → ${redirect.to} (ID: ${redirect.id})\n`;
|
|
70
|
+
content += ` Status: ${redirect.status}\n`;
|
|
71
|
+
if (redirect.created_at) {
|
|
72
|
+
content += ` Created: ${new Date(redirect.created_at).toLocaleDateString()}\n`;
|
|
73
|
+
}
|
|
74
|
+
content += '\n';
|
|
75
|
+
}
|
|
76
|
+
return { content: [{ type: 'text', text: content.trim() }] };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: `Error fetching redirects: ${error instanceof Error ? error.message : String(error)}`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
isError: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|