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.
Files changed (41) hide show
  1. package/README.md +16 -6
  2. package/build/shared/src/pulsemcp-admin-client/lib/create-redirect.js +41 -0
  3. package/build/shared/src/pulsemcp-admin-client/lib/delete-redirect.js +24 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/get-redirect.js +31 -0
  5. package/build/shared/src/pulsemcp-admin-client/lib/get-redirects.js +52 -0
  6. package/build/shared/src/pulsemcp-admin-client/lib/update-redirect.js +47 -0
  7. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +37 -0
  8. package/build/shared/src/server.js +21 -0
  9. package/build/shared/src/tools/create-redirect.js +76 -0
  10. package/build/shared/src/tools/delete-redirect.js +51 -0
  11. package/build/shared/src/tools/get-redirect.js +65 -0
  12. package/build/shared/src/tools/get-redirects.js +91 -0
  13. package/build/shared/src/tools/update-redirect.js +97 -0
  14. package/build/shared/src/tools.js +17 -0
  15. package/package.json +1 -1
  16. package/shared/pulsemcp-admin-client/lib/create-redirect.d.ts +3 -0
  17. package/shared/pulsemcp-admin-client/lib/create-redirect.js +41 -0
  18. package/shared/pulsemcp-admin-client/lib/delete-redirect.d.ts +5 -0
  19. package/shared/pulsemcp-admin-client/lib/delete-redirect.js +24 -0
  20. package/shared/pulsemcp-admin-client/lib/get-redirect.d.ts +3 -0
  21. package/shared/pulsemcp-admin-client/lib/get-redirect.js +31 -0
  22. package/shared/pulsemcp-admin-client/lib/get-redirects.d.ts +8 -0
  23. package/shared/pulsemcp-admin-client/lib/get-redirects.js +52 -0
  24. package/shared/pulsemcp-admin-client/lib/update-redirect.d.ts +3 -0
  25. package/shared/pulsemcp-admin-client/lib/update-redirect.js +47 -0
  26. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +37 -0
  27. package/shared/server.d.ts +27 -1
  28. package/shared/server.js +21 -0
  29. package/shared/tools/create-redirect.d.ts +39 -0
  30. package/shared/tools/create-redirect.js +76 -0
  31. package/shared/tools/delete-redirect.d.ts +30 -0
  32. package/shared/tools/delete-redirect.js +51 -0
  33. package/shared/tools/get-redirect.d.ts +30 -0
  34. package/shared/tools/get-redirect.js +65 -0
  35. package/shared/tools/get-redirects.d.ts +45 -0
  36. package/shared/tools/get-redirects.js +91 -0
  37. package/shared/tools/update-redirect.d.ts +43 -0
  38. package/shared/tools/update-redirect.js +97 -0
  39. package/shared/tools.d.ts +4 -1
  40. package/shared/tools.js +17 -0
  41. 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
+ }