pulsemcp-cms-admin-mcp-server 0.1.0 → 0.1.1

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 (37) hide show
  1. package/README.md +50 -18
  2. package/build/local/src/index.integration-with-mock.js +36 -0
  3. package/build/shared/src/pulsemcp-admin-client/lib/get-draft-mcp-implementations.js +61 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +73 -0
  5. package/build/shared/src/pulsemcp-admin-client/lib/search-mcp-implementations.js +51 -0
  6. package/build/shared/src/pulsemcp-admin-client/lib/send-email.js +46 -0
  7. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +120 -0
  8. package/build/shared/src/server.js +16 -0
  9. package/build/shared/src/tools/get-draft-mcp-implementations.js +167 -0
  10. package/build/shared/src/tools/save-mcp-implementation.js +223 -0
  11. package/build/shared/src/tools/search-mcp-implementations.js +155 -0
  12. package/build/shared/src/tools/send-mcp-implementation-posting-notification.js +207 -0
  13. package/build/shared/src/tools.js +56 -9
  14. package/package.json +1 -1
  15. package/shared/pulsemcp-admin-client/lib/get-draft-mcp-implementations.d.ts +6 -0
  16. package/shared/pulsemcp-admin-client/lib/get-draft-mcp-implementations.js +61 -0
  17. package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.d.ts +3 -0
  18. package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +73 -0
  19. package/shared/pulsemcp-admin-client/lib/search-mcp-implementations.d.ts +9 -0
  20. package/shared/pulsemcp-admin-client/lib/search-mcp-implementations.js +51 -0
  21. package/shared/pulsemcp-admin-client/lib/send-email.d.ts +25 -0
  22. package/shared/pulsemcp-admin-client/lib/send-email.js +46 -0
  23. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.d.ts +8 -1
  24. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +120 -0
  25. package/shared/server.d.ts +65 -1
  26. package/shared/server.js +16 -0
  27. package/shared/tools/get-draft-mcp-implementations.d.ts +33 -0
  28. package/shared/tools/get-draft-mcp-implementations.js +167 -0
  29. package/shared/tools/save-mcp-implementation.d.ts +85 -0
  30. package/shared/tools/save-mcp-implementation.js +223 -0
  31. package/shared/tools/search-mcp-implementations.d.ts +48 -0
  32. package/shared/tools/search-mcp-implementations.js +155 -0
  33. package/shared/tools/send-mcp-implementation-posting-notification.d.ts +46 -0
  34. package/shared/tools/send-mcp-implementation-posting-notification.js +207 -0
  35. package/shared/tools.d.ts +25 -1
  36. package/shared/tools.js +56 -9
  37. package/shared/types.d.ts +46 -0
package/README.md CHANGED
@@ -30,20 +30,38 @@ This is an MCP ([Model Context Protocol](https://modelcontextprotocol.io/)) Serv
30
30
 
31
31
  **Content Search**: Find newsletter posts with powerful search and pagination capabilities.
32
32
 
33
+ **MCP Implementation Search**: Search for MCP servers and clients in the PulseMCP registry.
34
+
35
+ **Toolgroups**: Enable/disable tool groups (newsletter, server_queue_readonly, server_queue_all) via environment variable.
36
+
33
37
  **Draft Control**: Manage draft posts before publishing to the newsletter.
34
38
 
35
39
  # Capabilities
36
40
 
37
41
  This server is built and tested on macOS with Claude Desktop. It should work with other MCP clients as well.
38
42
 
39
- | Tool Name | Description |
40
- | ------------------------ | -------------------------------------------------------------------------- |
41
- | `get_newsletter_posts` | List newsletter posts with search, sorting, and pagination options. |
42
- | `get_newsletter_post` | Retrieve a specific newsletter post by its unique slug. |
43
- | `draft_newsletter_post` | Create a new draft newsletter post with title, body, and metadata. |
44
- | `update_newsletter_post` | Update an existing newsletter post's content and metadata (except status). |
45
- | `upload_image` | Upload an image and attach it to a specific newsletter post. |
46
- | `get_authors` | Get a list of authors with optional search and pagination. |
43
+ | Tool Name | Tool Group | Description |
44
+ | ---------------------------------------------- | --------------------- | ---------------------------------------------------------------------------- |
45
+ | `get_newsletter_posts` | newsletter | List newsletter posts with search, sorting, and pagination options. |
46
+ | `get_newsletter_post` | newsletter | Retrieve a specific newsletter post by its unique slug. |
47
+ | `draft_newsletter_post` | newsletter | Create a new draft newsletter post with title, body, and metadata. |
48
+ | `update_newsletter_post` | newsletter | Update an existing newsletter post's content and metadata (except status). |
49
+ | `upload_image` | newsletter | Upload an image and attach it to a specific newsletter post. |
50
+ | `get_authors` | newsletter | Get a list of authors with optional search and pagination. |
51
+ | `search_mcp_implementations` | server_queue_readonly | Search for MCP servers and clients in the PulseMCP registry. |
52
+ | `get_draft_mcp_implementations` | server_queue_readonly | Retrieve paginated list of draft MCP implementations needing review. |
53
+ | `save_mcp_implementation` | server_queue_all | Update an MCP implementation (replicates Admin panel "Save Changes" button). |
54
+ | `send_mcp_implementation_posting_notification` | server_queue_all | Send email notification when MCP implementation goes live. |
55
+
56
+ # Tool Groups
57
+
58
+ This server organizes tools into groups that can be selectively enabled or disabled:
59
+
60
+ - **newsletter** (6 tools): Newsletter management, image uploads, and author retrieval
61
+ - **server_queue_readonly** (2 tools): Read-only MCP implementation tools (search, draft retrieval)
62
+ - **server_queue_all** (4 tools): All MCP implementation tools including write operations (search, draft retrieval, update, and email notification)
63
+
64
+ You can control which tool groups are available by setting the `PULSEMCP_ADMIN_ENABLED_TOOLGROUPS` environment variable as a comma-separated list (e.g., `newsletter,server_queue_readonly`). If not set, all tool groups are enabled by default.
47
65
 
48
66
  # Usage Tips
49
67
 
@@ -54,6 +72,8 @@ This server is built and tested on macOS with Claude Desktop. It should work wit
54
72
  - The update tool allows modifying all post metadata except status
55
73
  - Use author slugs when creating posts (e.g., "sarah-chen", "john-doe")
56
74
  - Use MCP server/client slugs for featured content (e.g., "github-mcp", "claude-desktop")
75
+ - Use `search_mcp_implementations` to discover MCP servers and clients in the PulseMCP registry
76
+ - Enable or disable specific toolgroups by setting `PULSEMCP_ADMIN_ENABLED_TOOLGROUPS` environment variable
57
77
 
58
78
  # Examples
59
79
 
@@ -80,9 +100,7 @@ I found 3 newsletter posts about MCP servers:
80
100
  ## Create a Draft Post
81
101
 
82
102
  ```
83
- User: Create a draft newsletter post about the new weather MCP server
84
-
85
- Assistant: I'll create a draft newsletter post about the new weather MCP server.
103
+ User: Create a draft newsletter post about the new weather MCP serverAssistant: I'll create a draft newsletter post about the new weather MCP server.
86
104
 
87
105
  [Calls draft_newsletter_post with title, body, slug, and author_slug]
88
106
 
@@ -92,9 +110,7 @@ I've created a draft newsletter post titled "New Weather MCP Server: Real-time F
92
110
  ## Upload and Attach an Image
93
111
 
94
112
  ```
95
- User: Upload this screenshot to the weather server post I just created
96
-
97
- Assistant: I'll upload the screenshot and attach it to your weather server post.
113
+ User: Upload this screenshot to the weather server post I just createdAssistant: I'll upload the screenshot and attach it to your weather server post.
98
114
 
99
115
  [Calls upload_image with post_slug="weather-mcp-server-launch" and file_name="weather-dashboard.png"]
100
116
 
@@ -104,15 +120,30 @@ The screenshot has been successfully uploaded and attached to the "weather-mcp-s
104
120
  ## Update an Existing Post
105
121
 
106
122
  ```
107
- User: Update the weather post to include information about the new temperature alerts feature
108
-
109
- Assistant: I'll update the weather MCP server post to include information about the temperature alerts feature.
123
+ User: Update the weather post to include information about the new temperature alerts featureAssistant: I'll update the weather MCP server post to include information about the temperature alerts feature.
110
124
 
111
125
  [Calls update_newsletter_post with slug="weather-mcp-server-launch" and updated content]
112
126
 
113
127
  I've successfully updated the "New Weather MCP Server" post to include a section about the temperature alerts feature. The post now covers both the real-time forecasts and the new alert system.
114
128
  ```
115
129
 
130
+ ## Search for MCP Implementations
131
+
132
+ ```
133
+ User: Find MCP servers related to weather or climate
134
+ Assistant: I'll search for MCP servers related to weather and climate.
135
+
136
+ [Calls search_mcp_implementations with search="weather climate"]
137
+
138
+ I found 2 MCP servers related to weather and climate:
139
+
140
+ 1. **Weather MCP Server** (weather-mcp)
141
+ - Real-time weather data and forecasting capabilities
142
+
143
+ 2. **Climate Data MCP Client** (climate-data-mcp)
144
+ - Access climate research and environmental data
145
+ ```
146
+
116
147
  # Setup
117
148
 
118
149
  ## Cheatsheet
@@ -148,7 +179,8 @@ Add to your Claude Desktop configuration:
148
179
  "command": "node",
149
180
  "args": ["/path/to/pulsemcp-cms-admin/local/build/index.js"],
150
181
  "env": {
151
- "PULSEMCP_ADMIN_API_KEY": "your-api-key-here"
182
+ "PULSEMCP_ADMIN_API_KEY": "your-api-key-here",
183
+ "PULSEMCP_ADMIN_ENABLED_TOOLGROUPS": "newsletter,server_queue_readonly,server_queue_all"
152
184
  }
153
185
  }
154
186
  }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Integration Test Server Entry Point with Mock Client Factory
4
+ *
5
+ * This file is used for integration testing with a mocked PulseMCPAdminClient.
6
+ * It uses the real MCP server but injects a mock client factory.
7
+ *
8
+ * Mock data is passed via the PULSEMCP_MOCK_DATA environment variable.
9
+ */
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ // IMPORTANT: This uses the package name pattern, not a relative path
12
+ import { createMCPServer } from 'pulsemcp-cms-admin-mcp-server-shared';
13
+ // Import the mock client factory from the shared module
14
+ import { createMockPulseMCPAdminClient } from '../../shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js';
15
+ async function main() {
16
+ const transport = new StdioServerTransport();
17
+ // Parse mock data from environment variable
18
+ let mockData = {};
19
+ if (process.env.PULSEMCP_MOCK_DATA) {
20
+ try {
21
+ mockData = JSON.parse(process.env.PULSEMCP_MOCK_DATA);
22
+ }
23
+ catch (e) {
24
+ console.error('Failed to parse PULSEMCP_MOCK_DATA:', e);
25
+ }
26
+ }
27
+ // Create client factory that returns our mock
28
+ const clientFactory = () => createMockPulseMCPAdminClient(mockData);
29
+ const { server, registerHandlers } = createMCPServer();
30
+ await registerHandlers(server, clientFactory);
31
+ await server.connect(transport);
32
+ }
33
+ main().catch((error) => {
34
+ console.error('Server error:', error);
35
+ process.exit(1);
36
+ });
@@ -0,0 +1,61 @@
1
+ export async function getDraftMCPImplementations(apiKey, baseUrl, params) {
2
+ const url = new URL('/api/implementations/drafts', baseUrl);
3
+ // Add query parameters if provided
4
+ if (params?.page) {
5
+ url.searchParams.append('page', params.page.toString());
6
+ }
7
+ if (params?.search) {
8
+ url.searchParams.append('search', params.search);
9
+ }
10
+ const response = await fetch(url.toString(), {
11
+ method: 'GET',
12
+ headers: {
13
+ 'X-API-Key': apiKey,
14
+ Accept: 'application/json',
15
+ },
16
+ });
17
+ if (!response.ok) {
18
+ if (response.status === 401) {
19
+ throw new Error('Invalid API key');
20
+ }
21
+ if (response.status === 403) {
22
+ throw new Error('User lacks admin privileges');
23
+ }
24
+ throw new Error(`Failed to fetch draft implementations: ${response.status} ${response.statusText}`);
25
+ }
26
+ // Parse the JSON response
27
+ const data = (await response.json());
28
+ // Handle the Rails JSON structure with data and meta
29
+ if (data.data && data.meta) {
30
+ return {
31
+ implementations: data.data.map((impl) => ({
32
+ id: impl.id,
33
+ name: impl.name,
34
+ short_description: impl.short_description,
35
+ description: impl.description,
36
+ type: impl.type,
37
+ status: impl.status,
38
+ slug: impl.slug,
39
+ url: impl.url,
40
+ provider_name: impl.provider_name,
41
+ github_stars: impl.github_stars,
42
+ classification: impl.classification,
43
+ implementation_language: impl.implementation_language,
44
+ mcp_server_id: impl.mcp_server_id,
45
+ mcp_client_id: impl.mcp_client_id,
46
+ created_at: impl.created_at,
47
+ updated_at: impl.updated_at,
48
+ })),
49
+ pagination: {
50
+ current_page: data.meta.current_page,
51
+ total_pages: data.meta.total_pages,
52
+ total_count: data.meta.total_count,
53
+ },
54
+ };
55
+ }
56
+ // Fallback for unexpected response format
57
+ return {
58
+ implementations: [],
59
+ pagination: undefined,
60
+ };
61
+ }
@@ -0,0 +1,73 @@
1
+ export async function saveMCPImplementation(apiKey, baseUrl, id, params) {
2
+ const url = new URL(`/api/implementations/${id}`, baseUrl);
3
+ // Build form data for the PUT request
4
+ const formData = new URLSearchParams();
5
+ // Add all provided fields using Rails conventions
6
+ if (params.name !== undefined) {
7
+ formData.append('mcp_implementation[name]', params.name);
8
+ }
9
+ if (params.short_description !== undefined) {
10
+ formData.append('mcp_implementation[short_description]', params.short_description);
11
+ }
12
+ if (params.description !== undefined) {
13
+ formData.append('mcp_implementation[description]', params.description);
14
+ }
15
+ if (params.type !== undefined) {
16
+ formData.append('mcp_implementation[type]', params.type);
17
+ }
18
+ if (params.status !== undefined) {
19
+ formData.append('mcp_implementation[status]', params.status);
20
+ }
21
+ if (params.slug !== undefined) {
22
+ formData.append('mcp_implementation[slug]', params.slug);
23
+ }
24
+ if (params.url !== undefined) {
25
+ formData.append('mcp_implementation[url]', params.url);
26
+ }
27
+ if (params.provider_name !== undefined) {
28
+ formData.append('mcp_implementation[provider_name]', params.provider_name);
29
+ }
30
+ if (params.github_stars !== undefined) {
31
+ formData.append('mcp_implementation[github_stars]', params.github_stars.toString());
32
+ }
33
+ if (params.classification !== undefined) {
34
+ formData.append('mcp_implementation[classification]', params.classification);
35
+ }
36
+ if (params.implementation_language !== undefined) {
37
+ formData.append('mcp_implementation[implementation_language]', params.implementation_language);
38
+ }
39
+ if (params.mcp_server_id !== undefined) {
40
+ formData.append('mcp_implementation[mcp_server_id]', params.mcp_server_id === null ? '' : params.mcp_server_id.toString());
41
+ }
42
+ if (params.mcp_client_id !== undefined) {
43
+ formData.append('mcp_implementation[mcp_client_id]', params.mcp_client_id === null ? '' : params.mcp_client_id.toString());
44
+ }
45
+ const response = await fetch(url.toString(), {
46
+ method: 'PUT',
47
+ headers: {
48
+ 'X-API-Key': apiKey,
49
+ 'Content-Type': 'application/x-www-form-urlencoded',
50
+ Accept: 'application/json',
51
+ },
52
+ body: formData.toString(),
53
+ });
54
+ if (!response.ok) {
55
+ if (response.status === 401) {
56
+ throw new Error('Invalid API key');
57
+ }
58
+ if (response.status === 403) {
59
+ throw new Error('User lacks admin privileges');
60
+ }
61
+ if (response.status === 404) {
62
+ throw new Error(`MCP implementation not found: ${id}`);
63
+ }
64
+ if (response.status === 422) {
65
+ const errorData = (await response.json());
66
+ const errors = errorData.errors || ['Validation failed'];
67
+ throw new Error(`Validation failed: ${errors.join(', ')}`);
68
+ }
69
+ throw new Error(`Failed to save MCP implementation: ${response.status} ${response.statusText}`);
70
+ }
71
+ const data = await response.json();
72
+ return data;
73
+ }
@@ -0,0 +1,51 @@
1
+ export async function searchMCPImplementations(apiKey, baseUrl, params) {
2
+ // Endpoint implemented at: /api/implementations/search
3
+ // Requires admin authentication via X-API-Key header
4
+ const url = new URL('/api/implementations/search', baseUrl);
5
+ // Add query parameters
6
+ url.searchParams.append('q', params.query);
7
+ if (params.type && params.type !== 'all') {
8
+ url.searchParams.append('type', params.type);
9
+ }
10
+ if (params.status && params.status !== 'all') {
11
+ url.searchParams.append('status', params.status);
12
+ }
13
+ if (params.limit) {
14
+ url.searchParams.append('limit', params.limit.toString());
15
+ }
16
+ if (params.offset) {
17
+ url.searchParams.append('offset', params.offset.toString());
18
+ }
19
+ const response = await fetch(url.toString(), {
20
+ method: 'GET',
21
+ headers: {
22
+ 'X-API-Key': apiKey,
23
+ Accept: 'application/json',
24
+ },
25
+ });
26
+ if (!response.ok) {
27
+ if (response.status === 401) {
28
+ throw new Error('Invalid API key');
29
+ }
30
+ if (response.status === 403) {
31
+ throw new Error('User lacks admin privileges');
32
+ }
33
+ if (response.status === 404) {
34
+ throw new Error('MCP implementations search endpoint not found');
35
+ }
36
+ throw new Error(`Failed to search MCP implementations: ${response.status} ${response.statusText}`);
37
+ }
38
+ const data = (await response.json());
39
+ return {
40
+ implementations: data.data || [],
41
+ pagination: data.meta
42
+ ? {
43
+ current_page: data.meta.current_page,
44
+ total_pages: data.meta.total_pages,
45
+ total_count: data.meta.total_count,
46
+ has_next: data.meta.has_next,
47
+ limit: data.meta.limit,
48
+ }
49
+ : undefined,
50
+ };
51
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Send an email via the PulseMCP Admin API
3
+ */
4
+ export async function sendEmail(apiKey, baseUrl, params) {
5
+ const url = new URL('/emails', baseUrl);
6
+ // Create form data for Rails-style parameters
7
+ const formData = new URLSearchParams();
8
+ formData.append('email[from_email_address]', params.from_email_address);
9
+ formData.append('email[from_name]', params.from_name);
10
+ formData.append('email[reply_to_email_address]', params.reply_to_email_address);
11
+ formData.append('email[to_email_address]', params.to_email_address);
12
+ formData.append('email[subject]', params.subject);
13
+ formData.append('email[content]', params.content);
14
+ const response = await fetch(url.toString(), {
15
+ method: 'POST',
16
+ headers: {
17
+ 'X-API-Key': apiKey,
18
+ 'Content-Type': 'application/x-www-form-urlencoded',
19
+ Accept: 'application/json',
20
+ },
21
+ body: formData.toString(),
22
+ });
23
+ if (!response.ok) {
24
+ const errorBody = await response.text();
25
+ let errorMessage;
26
+ try {
27
+ const errorData = JSON.parse(errorBody);
28
+ if (errorData.errors) {
29
+ errorMessage = Array.isArray(errorData.errors)
30
+ ? errorData.errors.join(', ')
31
+ : JSON.stringify(errorData.errors);
32
+ }
33
+ else if (errorData.error) {
34
+ errorMessage = errorData.error;
35
+ }
36
+ else {
37
+ errorMessage = errorBody;
38
+ }
39
+ }
40
+ catch {
41
+ errorMessage = errorBody || `HTTP ${response.status}: ${response.statusText}`;
42
+ }
43
+ throw new Error(`Failed to send email: ${errorMessage}`);
44
+ }
45
+ return response.json();
46
+ }
@@ -201,5 +201,125 @@ export function createMockPulseMCPAdminClient(mockData) {
201
201
  }
202
202
  return null;
203
203
  },
204
+ async searchMCPImplementations(params) {
205
+ if (mockData.errors?.searchMCPImplementations) {
206
+ throw mockData.errors.searchMCPImplementations;
207
+ }
208
+ if (mockData.implementationsResponse) {
209
+ return mockData.implementationsResponse;
210
+ }
211
+ let implementations = mockData.implementations || [
212
+ {
213
+ id: 1,
214
+ name: 'Test MCP Server',
215
+ slug: 'test-mcp-server',
216
+ type: 'server',
217
+ status: 'live',
218
+ short_description: 'A test MCP server implementation',
219
+ classification: 'community',
220
+ implementation_language: 'TypeScript',
221
+ },
222
+ ];
223
+ // Apply search filter
224
+ if (params.query) {
225
+ const query = params.query.toLowerCase();
226
+ implementations = implementations.filter((impl) => impl.name.toLowerCase().includes(query) ||
227
+ impl.slug.toLowerCase().includes(query) ||
228
+ (impl.short_description && impl.short_description.toLowerCase().includes(query)) ||
229
+ (impl.description && impl.description.toLowerCase().includes(query)) ||
230
+ (impl.provider_name && impl.provider_name.toLowerCase().includes(query)));
231
+ }
232
+ // Apply type filter
233
+ if (params.type && params.type !== 'all') {
234
+ implementations = implementations.filter((impl) => impl.type === params.type);
235
+ }
236
+ // Apply status filter
237
+ if (params.status && params.status !== 'all') {
238
+ implementations = implementations.filter((impl) => impl.status === params.status);
239
+ }
240
+ // Apply pagination
241
+ const offset = params.offset || 0;
242
+ const limit = params.limit || 30;
243
+ const totalCount = implementations.length;
244
+ const paginatedImpls = implementations.slice(offset, offset + limit);
245
+ return {
246
+ implementations: paginatedImpls,
247
+ pagination: {
248
+ current_page: Math.floor(offset / limit) + 1,
249
+ total_pages: Math.ceil(totalCount / limit),
250
+ total_count: totalCount,
251
+ has_next: offset + limit < totalCount,
252
+ limit: limit,
253
+ },
254
+ };
255
+ },
256
+ async getDraftMCPImplementations(params) {
257
+ if (mockData.errors?.getDraftMCPImplementations) {
258
+ throw mockData.errors.getDraftMCPImplementations;
259
+ }
260
+ if (mockData.draftImplementationsResponse) {
261
+ return mockData.draftImplementationsResponse;
262
+ }
263
+ // Filter to only draft implementations
264
+ let implementations = (mockData.implementations || []).filter((impl) => impl.status === 'draft');
265
+ // Apply search filter if provided
266
+ if (params?.search) {
267
+ const searchLower = params.search.toLowerCase();
268
+ implementations = implementations.filter((impl) => impl.name.toLowerCase().includes(searchLower) ||
269
+ impl.short_description?.toLowerCase().includes(searchLower) ||
270
+ impl.description?.toLowerCase().includes(searchLower));
271
+ }
272
+ // Apply pagination
273
+ const page = params?.page || 1;
274
+ const perPage = 20; // Match Rails default
275
+ const offset = (page - 1) * perPage;
276
+ const paginatedImpls = implementations.slice(offset, offset + perPage);
277
+ return {
278
+ implementations: paginatedImpls,
279
+ pagination: {
280
+ current_page: page,
281
+ total_pages: Math.ceil(implementations.length / perPage),
282
+ total_count: implementations.length,
283
+ },
284
+ };
285
+ },
286
+ async saveMCPImplementation(id, params) {
287
+ if (mockData.errors?.saveMCPImplementation) {
288
+ throw mockData.errors.saveMCPImplementation;
289
+ }
290
+ // Find existing implementation in mock data
291
+ const implementations = mockData.implementations || [];
292
+ const existingImpl = implementations.find((impl) => impl.id === id);
293
+ if (!existingImpl) {
294
+ throw new Error(`MCP implementation not found: ${id}`);
295
+ }
296
+ // Merge params with existing implementation
297
+ const updatedImpl = {
298
+ ...existingImpl,
299
+ ...params,
300
+ id: id, // Ensure ID doesn't change
301
+ updated_at: new Date().toISOString(),
302
+ };
303
+ return updatedImpl;
304
+ },
305
+ async sendEmail(params) {
306
+ if (mockData.errors?.sendEmail) {
307
+ throw mockData.errors.sendEmail;
308
+ }
309
+ // Mock successful email response
310
+ return {
311
+ id: Math.floor(Math.random() * 10000),
312
+ sender_provider: 'sendgrid',
313
+ send_timestamp_utc: new Date().toISOString(),
314
+ from_email_address: params.from_email_address,
315
+ to_email_address: params.to_email_address,
316
+ subject: params.subject,
317
+ content_text: params.content,
318
+ content_html: `<html><body>${params.content.replace(/\n/g, '<br>')}</body></html>`,
319
+ campaign_identifier: `admin-api-email-${Date.now()}`,
320
+ created_at: new Date().toISOString(),
321
+ updated_at: new Date().toISOString(),
322
+ };
323
+ },
204
324
  };
205
325
  }
@@ -55,6 +55,22 @@ export class PulseMCPAdminClient {
55
55
  const { getMCPClientById } = await import('./pulsemcp-admin-client/lib/get-mcp-client-by-id.js');
56
56
  return getMCPClientById(this.apiKey, this.baseUrl, id);
57
57
  }
58
+ async searchMCPImplementations(params) {
59
+ const { searchMCPImplementations } = await import('./pulsemcp-admin-client/lib/search-mcp-implementations.js');
60
+ return searchMCPImplementations(this.apiKey, this.baseUrl, params);
61
+ }
62
+ async getDraftMCPImplementations(params) {
63
+ const { getDraftMCPImplementations } = await import('./pulsemcp-admin-client/lib/get-draft-mcp-implementations.js');
64
+ return getDraftMCPImplementations(this.apiKey, this.baseUrl, params);
65
+ }
66
+ async saveMCPImplementation(id, params) {
67
+ const { saveMCPImplementation } = await import('./pulsemcp-admin-client/lib/save-mcp-implementation.js');
68
+ return saveMCPImplementation(this.apiKey, this.baseUrl, id, params);
69
+ }
70
+ async sendEmail(params) {
71
+ const { sendEmail } = await import('./pulsemcp-admin-client/lib/send-email.js');
72
+ return sendEmail(this.apiKey, this.baseUrl, params);
73
+ }
58
74
  }
59
75
  export function createMCPServer() {
60
76
  const server = new Server({