pulsemcp-cms-admin-mcp-server 0.6.0 → 0.6.2

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 (34) hide show
  1. package/README.md +78 -34
  2. package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +93 -0
  3. package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +51 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +27 -0
  5. package/build/shared/src/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +81 -0
  6. package/build/shared/src/pulsemcp-admin-client/lib/update-unified-mcp-server.js +90 -0
  7. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +46 -0
  8. package/build/shared/src/server.js +13 -0
  9. package/build/shared/src/tools/get-mcp-server.js +246 -0
  10. package/build/shared/src/tools/list-mcp-servers.js +146 -0
  11. package/build/shared/src/tools/update-mcp-server.js +340 -0
  12. package/build/shared/src/tools.js +15 -0
  13. package/package.json +1 -1
  14. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.d.ts +7 -0
  15. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +93 -0
  16. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-servers.d.ts +9 -0
  17. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +51 -0
  18. package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +27 -0
  19. package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.d.ts +72 -0
  20. package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +81 -0
  21. package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.d.ts +7 -0
  22. package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.js +90 -0
  23. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +46 -0
  24. package/shared/server.d.ts +19 -1
  25. package/shared/server.js +13 -0
  26. package/shared/tools/get-mcp-server.d.ts +30 -0
  27. package/shared/tools/get-mcp-server.js +246 -0
  28. package/shared/tools/list-mcp-servers.d.ts +50 -0
  29. package/shared/tools/list-mcp-servers.js +146 -0
  30. package/shared/tools/update-mcp-server.d.ts +199 -0
  31. package/shared/tools/update-mcp-server.js +340 -0
  32. package/shared/tools.d.ts +4 -1
  33. package/shared/tools.js +15 -0
  34. package/shared/types.d.ts +118 -0
package/README.md CHANGED
@@ -40,26 +40,43 @@ This is an MCP ([Model Context Protocol](https://modelcontextprotocol.io/)) Serv
40
40
 
41
41
  This server is built and tested on macOS with Claude Desktop. It should work with other MCP clients as well.
42
42
 
43
- | Tool Name | Tool Group | Read/Write | Description |
44
- | -------------------------------------- | -------------- | ---------- | ---------------------------------------------------------------------------- |
45
- | `get_newsletter_posts` | newsletter | read | List newsletter posts with search, sorting, and pagination options. |
46
- | `get_newsletter_post` | newsletter | read | Retrieve a specific newsletter post by its unique slug. |
47
- | `draft_newsletter_post` | newsletter | write | Create a new draft newsletter post with title, body, and metadata. |
48
- | `update_newsletter_post` | newsletter | write | Update an existing newsletter post's content and metadata (except status). |
49
- | `upload_image` | newsletter | write | Upload an image and attach it to a specific newsletter post. |
50
- | `get_authors` | newsletter | read | Get a list of authors with optional search and pagination. |
51
- | `search_mcp_implementations` | server_queue | read | Search for MCP servers and clients in the PulseMCP registry. |
52
- | `get_draft_mcp_implementations` | server_queue | read | Retrieve paginated list of draft MCP implementations needing review. |
53
- | `find_providers` | server_queue | read | Search for providers by ID, name, URL, or slug. |
54
- | `save_mcp_implementation` | server_queue | write | Update an MCP implementation (replicates Admin panel "Save Changes" button). |
55
- | `send_impl_posted_notif` | server_queue | write | Send email notification when MCP implementation goes live. |
56
- | `get_official_mirror_queue_items` | official_queue | read | List and filter official mirror queue entries with pagination and search. |
57
- | `get_official_mirror_queue_item` | official_queue | read | Get detailed information about a single official mirror queue entry. |
58
- | `approve_official_mirror_queue_item` | official_queue | write | Approve a queue entry and link it to an existing MCP server (async). |
59
- | `approve_mirror_no_modify` | official_queue | write | Approve without updating the linked server. |
60
- | `reject_official_mirror_queue_item` | official_queue | write | Reject a queue entry (async operation). |
61
- | `add_official_mirror_to_regular_queue` | official_queue | write | Convert a queue entry to a draft MCP implementation (async). |
62
- | `unlink_official_mirror_queue_item` | official_queue | write | Unlink a queue entry from its linked MCP server. |
43
+ | Tool Name | Tool Group | Read/Write | Description |
44
+ | -------------------------------------- | ------------------ | ---------- | ----------------------------------------------------------------------------- |
45
+ | `get_newsletter_posts` | newsletter | read | List newsletter posts with search, sorting, and pagination options. |
46
+ | `get_newsletter_post` | newsletter | read | Retrieve a specific newsletter post by its unique slug. |
47
+ | `draft_newsletter_post` | newsletter | write | Create a new draft newsletter post with title, body, and metadata. |
48
+ | `update_newsletter_post` | newsletter | write | Update an existing newsletter post's content and metadata (except status). |
49
+ | `upload_image` | newsletter | write | Upload an image and attach it to a specific newsletter post. |
50
+ | `get_authors` | newsletter | read | Get a list of authors with optional search and pagination. |
51
+ | `search_mcp_implementations` | server_queue | read | Search for MCP servers and clients in the PulseMCP registry. |
52
+ | `get_draft_mcp_implementations` | server_queue | read | Retrieve paginated list of draft MCP implementations needing review. |
53
+ | `find_providers` | server_queue | read | Search for providers by ID, name, URL, or slug. |
54
+ | `save_mcp_implementation` | server_queue | write | Update an MCP implementation (replicates Admin panel "Save Changes" button). |
55
+ | `send_impl_posted_notif` | server_queue | write | Send email notification when MCP implementation goes live. |
56
+ | `get_official_mirror_queue_items` | official_queue | read | List and filter official mirror queue entries with pagination and search. |
57
+ | `get_official_mirror_queue_item` | official_queue | read | Get detailed information about a single official mirror queue entry. |
58
+ | `approve_official_mirror_queue_item` | official_queue | write | Approve a queue entry and link it to an existing MCP server (async). |
59
+ | `approve_mirror_no_modify` | official_queue | write | Approve without updating the linked server. |
60
+ | `reject_official_mirror_queue_item` | official_queue | write | Reject a queue entry (async operation). |
61
+ | `add_official_mirror_to_regular_queue` | official_queue | write | Convert a queue entry to a draft MCP implementation (async). |
62
+ | `unlink_official_mirror_queue_item` | official_queue | write | Unlink a queue entry from its linked MCP server. |
63
+ | `get_unofficial_mirrors` | unofficial_mirrors | read | List unofficial mirrors with search, pagination, and MCP server filtering. |
64
+ | `get_unofficial_mirror` | unofficial_mirrors | read | Get detailed unofficial mirror info by ID or name. |
65
+ | `create_unofficial_mirror` | unofficial_mirrors | write | Create a new unofficial mirror entry with JSON data. |
66
+ | `update_unofficial_mirror` | unofficial_mirrors | write | Update an existing unofficial mirror by ID. |
67
+ | `delete_unofficial_mirror` | unofficial_mirrors | write | Delete an unofficial mirror by ID (irreversible). |
68
+ | `get_official_mirrors` | official_mirrors | read | List official mirrors with search, status, and processing filters. |
69
+ | `get_official_mirror` | official_mirrors | read | Get detailed official mirror info by ID or name. |
70
+ | `get_tenants` | tenants | read | List tenants with search and admin status filtering. |
71
+ | `get_tenant` | tenants | read | Get detailed tenant info by ID or slug. |
72
+ | `get_mcp_jsons` | mcp_jsons | read | List MCP JSON configs with mirror and server filtering. |
73
+ | `get_mcp_json` | mcp_jsons | read | Get a single MCP JSON configuration by ID. |
74
+ | `create_mcp_json` | mcp_jsons | write | Create a new MCP JSON configuration for an unofficial mirror. |
75
+ | `update_mcp_json` | mcp_jsons | write | Update an existing MCP JSON configuration by ID. |
76
+ | `delete_mcp_json` | mcp_jsons | write | Delete an MCP JSON configuration by ID (irreversible). |
77
+ | `list_mcp_servers` | mcp_servers | read | List/search MCP servers with filtering by status, classification, pagination. |
78
+ | `get_mcp_server` | mcp_servers | read | Get detailed MCP server info by slug (unified view of all admin UI fields). |
79
+ | `update_mcp_server` | mcp_servers | write | Update an MCP server's fields (all admin UI fields supported). |
63
80
 
64
81
  # Tool Groups
65
82
 
@@ -70,14 +87,24 @@ This server organizes tools into groups that can be selectively enabled or disab
70
87
 
71
88
  ## Available Groups
72
89
 
73
- | Group | Tools | Description |
74
- | ------------------------- | ----- | -------------------------------------------- |
75
- | `newsletter` | 6 | Full newsletter management (read + write) |
76
- | `newsletter_readonly` | 3 | Newsletter read-only (get posts, authors) |
77
- | `server_queue` | 5 | Full MCP implementation queue (read + write) |
78
- | `server_queue_readonly` | 3 | MCP implementation queue read-only |
79
- | `official_queue` | 7 | Full official mirror queue (read + write) |
80
- | `official_queue_readonly` | 2 | Official mirror queue read-only |
90
+ | Group | Tools | Description |
91
+ | ----------------------------- | ----- | -------------------------------------------- |
92
+ | `newsletter` | 6 | Full newsletter management (read + write) |
93
+ | `newsletter_readonly` | 3 | Newsletter read-only (get posts, authors) |
94
+ | `server_queue` | 5 | Full MCP implementation queue (read + write) |
95
+ | `server_queue_readonly` | 3 | MCP implementation queue read-only |
96
+ | `official_queue` | 7 | Full official mirror queue (read + write) |
97
+ | `official_queue_readonly` | 2 | Official mirror queue read-only |
98
+ | `unofficial_mirrors` | 5 | Full unofficial mirrors CRUD (read + write) |
99
+ | `unofficial_mirrors_readonly` | 2 | Unofficial mirrors read-only |
100
+ | `official_mirrors` | 2 | Official mirrors REST API (read-only) |
101
+ | `official_mirrors_readonly` | 2 | Official mirrors read-only (alias) |
102
+ | `tenants` | 2 | Tenants REST API (read-only) |
103
+ | `tenants_readonly` | 2 | Tenants read-only (alias) |
104
+ | `mcp_jsons` | 5 | Full MCP JSON configurations (read + write) |
105
+ | `mcp_jsons_readonly` | 2 | MCP JSON configurations read-only |
106
+ | `mcp_servers` | 3 | Full MCP servers management (read + write) |
107
+ | `mcp_servers_readonly` | 2 | MCP servers read-only (list, get) |
81
108
 
82
109
  ### Tools by Group
83
110
 
@@ -90,12 +117,25 @@ This server organizes tools into groups that can be selectively enabled or disab
90
117
  - **official_queue** / **official_queue_readonly**:
91
118
  - Read-only: `get_official_mirror_queue_items`, `get_official_mirror_queue_item`
92
119
  - Write: `approve_official_mirror_queue_item`, `approve_mirror_no_modify`, `reject_official_mirror_queue_item`, `add_official_mirror_to_regular_queue`, `unlink_official_mirror_queue_item`
120
+ - **unofficial_mirrors** / **unofficial_mirrors_readonly**:
121
+ - Read-only: `get_unofficial_mirrors`, `get_unofficial_mirror`
122
+ - Write: `create_unofficial_mirror`, `update_unofficial_mirror`, `delete_unofficial_mirror`
123
+ - **official_mirrors** / **official_mirrors_readonly**:
124
+ - Read-only: `get_official_mirrors`, `get_official_mirror`
125
+ - **tenants** / **tenants_readonly**:
126
+ - Read-only: `get_tenants`, `get_tenant`
127
+ - **mcp_jsons** / **mcp_jsons_readonly**:
128
+ - Read-only: `get_mcp_jsons`, `get_mcp_json`
129
+ - Write: `create_mcp_json`, `update_mcp_json`, `delete_mcp_json`
130
+ - **mcp_servers** / **mcp_servers_readonly**:
131
+ - Read-only: `list_mcp_servers`, `get_mcp_server`
132
+ - Write: `update_mcp_server`
93
133
 
94
134
  ## Environment Variables
95
135
 
96
- | Variable | Description | Default |
97
- | ------------- | ------------------------------------------- | ---------------------------------------------------------- |
98
- | `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `newsletter,server_queue,official_queue` (all base groups) |
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) |
99
139
 
100
140
  ## Examples
101
141
 
@@ -120,7 +160,7 @@ TOOL_GROUPS=server_queue_readonly
120
160
  Enable all groups with read-only access:
121
161
 
122
162
  ```bash
123
- TOOL_GROUPS=newsletter_readonly,server_queue_readonly,official_queue_readonly
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
124
164
  ```
125
165
 
126
166
  Mix full and read-only access per group:
@@ -146,6 +186,10 @@ TOOL_GROUPS=newsletter,server_queue_readonly
146
186
  - Use the `canonical` array parameter in `save_mcp_implementation` to set canonical URLs with scope (domain, subdomain, subfolder, or url)
147
187
  - Remote endpoints allow specifying how MCP servers can be accessed (direct URL, setup URL, authentication method, cost, etc.)
148
188
  - When updating existing remotes, include the remote `id` (number from `get_draft_mcp_implementations`) in the remote object
189
+ - Use `list_mcp_servers` to browse MCP servers with filtering by status (draft/live/archived) and classification (official/community)
190
+ - Use `get_mcp_server` to get a unified view of all server data including provider, source code, canonicals, remotes, and tags
191
+ - Use `update_mcp_server` to update any admin UI field: name, description, provider, source code, package info, tags, canonicals, remotes, etc.
192
+ - The `mcp_servers` tools abstract away the MCPImplementation → MCPServer data model complexity
149
193
 
150
194
  # Examples
151
195
 
@@ -252,7 +296,7 @@ Add to your Claude Desktop configuration:
252
296
  "args": ["/path/to/pulsemcp-cms-admin/local/build/index.js"],
253
297
  "env": {
254
298
  "PULSEMCP_ADMIN_API_KEY": "your-api-key-here",
255
- "TOOL_GROUPS": "newsletter,server_queue,official_queue"
299
+ "TOOL_GROUPS": "newsletter,server_queue,official_queue,unofficial_mirrors,official_mirrors,tenants,mcp_jsons,mcp_servers"
256
300
  }
257
301
  }
258
302
  }
@@ -269,7 +313,7 @@ For read-only access:
269
313
  "args": ["/path/to/pulsemcp-cms-admin/local/build/index.js"],
270
314
  "env": {
271
315
  "PULSEMCP_ADMIN_API_KEY": "your-api-key-here",
272
- "TOOL_GROUPS": "newsletter_readonly,server_queue_readonly,official_queue_readonly"
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"
273
317
  }
274
318
  }
275
319
  }
@@ -0,0 +1,93 @@
1
+ import { mapToUnifiedServer } from './unified-mcp-server-mapper.js';
2
+ /**
3
+ * Get a unified MCP server by its slug.
4
+ * This fetches the MCPServer and its associated MCPImplementation data.
5
+ */
6
+ export async function getUnifiedMCPServer(apiKey, baseUrl, slug) {
7
+ // First, get the MCPServer to get its ID
8
+ const serverUrl = new URL(`/supervisor/mcp_servers/${slug}`, baseUrl);
9
+ const serverResponse = await fetch(serverUrl.toString(), {
10
+ method: 'GET',
11
+ headers: {
12
+ 'X-API-Key': apiKey,
13
+ Accept: 'application/json',
14
+ },
15
+ });
16
+ if (!serverResponse.ok) {
17
+ if (serverResponse.status === 401) {
18
+ throw new Error('Invalid API key');
19
+ }
20
+ if (serverResponse.status === 403) {
21
+ throw new Error('User lacks admin privileges');
22
+ }
23
+ if (serverResponse.status === 404) {
24
+ throw new Error(`MCP server not found: ${slug}`);
25
+ }
26
+ throw new Error(`Failed to fetch MCP server: ${serverResponse.status} ${serverResponse.statusText}`);
27
+ }
28
+ const mcpServerData = (await serverResponse.json());
29
+ // Now search for the implementation that references this MCPServer
30
+ // Use a search that includes the slug to narrow down results
31
+ // Don't pass status param to get all statuses (API doesn't support 'all' as a value)
32
+ const searchUrl = new URL('/api/implementations/search', baseUrl);
33
+ searchUrl.searchParams.append('q', slug);
34
+ searchUrl.searchParams.append('type', 'server');
35
+ searchUrl.searchParams.append('limit', '50');
36
+ const searchResponse = await fetch(searchUrl.toString(), {
37
+ method: 'GET',
38
+ headers: {
39
+ 'X-API-Key': apiKey,
40
+ Accept: 'application/json',
41
+ },
42
+ });
43
+ if (!searchResponse.ok) {
44
+ throw new Error(`Failed to fetch implementation data: ${searchResponse.status} ${searchResponse.statusText}`);
45
+ }
46
+ const searchData = (await searchResponse.json());
47
+ // Find the implementation that matches this MCPServer's ID
48
+ const matchingImpl = searchData.data.find((impl) => impl.mcp_server_id === mcpServerData.id);
49
+ if (!matchingImpl) {
50
+ // If no matching implementation found, construct a minimal unified server from MCPServer data
51
+ return {
52
+ id: mcpServerData.id,
53
+ slug: mcpServerData.slug,
54
+ implementation_id: null, // No implementation exists for this server
55
+ name: mcpServerData.slug, // Use slug as name fallback
56
+ status: 'draft',
57
+ classification: mcpServerData.classification,
58
+ implementation_language: mcpServerData.implementation_language,
59
+ remotes: mcpServerData.remotes?.map((r) => ({
60
+ id: r.id,
61
+ display_name: r.display_name,
62
+ url_direct: r.url_direct,
63
+ url_setup: r.url_setup,
64
+ transport: r.transport,
65
+ host_platform: r.host_platform,
66
+ host_infrastructure: r.host_infrastructure,
67
+ authentication_method: r.authentication_method,
68
+ cost: r.cost,
69
+ status: r.status,
70
+ internal_notes: r.internal_notes,
71
+ })),
72
+ tags: mcpServerData.tags,
73
+ registry_package_id: mcpServerData.registry_package_id,
74
+ registry_package_soft_verified: mcpServerData.registry_package_soft_verified,
75
+ downloads_estimate_last_7_days: mcpServerData.downloads_estimate_last_7_days,
76
+ downloads_estimate_last_30_days: mcpServerData.downloads_estimate_last_30_days,
77
+ downloads_estimate_total: mcpServerData.downloads_estimate_total,
78
+ created_at: mcpServerData.created_at,
79
+ updated_at: mcpServerData.updated_at,
80
+ };
81
+ }
82
+ // Merge MCPServer data with implementation data for complete picture
83
+ matchingImpl.mcp_server = {
84
+ ...mcpServerData,
85
+ remotes: mcpServerData.remotes,
86
+ tags: mcpServerData.tags,
87
+ };
88
+ const unified = mapToUnifiedServer(matchingImpl);
89
+ if (!unified) {
90
+ throw new Error(`Failed to map MCP server data for: ${slug}`);
91
+ }
92
+ return unified;
93
+ }
@@ -0,0 +1,51 @@
1
+ import { mapToUnifiedServer } from './unified-mcp-server-mapper.js';
2
+ export async function getUnifiedMCPServers(apiKey, baseUrl, params) {
3
+ const url = new URL('/api/implementations/search', baseUrl);
4
+ // Always filter to servers only
5
+ url.searchParams.append('type', 'server');
6
+ // The API requires a search query - use '*' wildcard if none provided
7
+ url.searchParams.append('q', params?.q || '*');
8
+ if (params?.status && params.status !== 'all') {
9
+ url.searchParams.append('status', params.status);
10
+ }
11
+ if (params?.classification) {
12
+ url.searchParams.append('classification', params.classification);
13
+ }
14
+ if (params?.limit) {
15
+ url.searchParams.append('limit', params.limit.toString());
16
+ }
17
+ if (params?.offset) {
18
+ url.searchParams.append('offset', params.offset.toString());
19
+ }
20
+ const response = await fetch(url.toString(), {
21
+ method: 'GET',
22
+ headers: {
23
+ 'X-API-Key': apiKey,
24
+ Accept: 'application/json',
25
+ },
26
+ });
27
+ if (!response.ok) {
28
+ if (response.status === 401) {
29
+ throw new Error('Invalid API key');
30
+ }
31
+ if (response.status === 403) {
32
+ throw new Error('User lacks admin privileges');
33
+ }
34
+ throw new Error(`Failed to fetch MCP servers: ${response.status} ${response.statusText}`);
35
+ }
36
+ const data = (await response.json());
37
+ // Map implementations to unified servers, filtering out those without an MCPServer
38
+ const servers = data.data
39
+ .map(mapToUnifiedServer)
40
+ .filter((s) => s !== null);
41
+ return {
42
+ servers,
43
+ pagination: {
44
+ current_page: data.meta.current_page,
45
+ total_pages: data.meta.total_pages,
46
+ total_count: data.meta.total_count,
47
+ has_next: data.meta.has_next,
48
+ limit: data.meta.limit,
49
+ },
50
+ };
51
+ }
@@ -63,6 +63,33 @@ export async function saveMCPImplementation(apiKey, baseUrl, id, params) {
63
63
  if (params.github_subfolder !== undefined) {
64
64
  formData.append('mcp_implementation[github_subfolder]', params.github_subfolder);
65
65
  }
66
+ // Package registry fields
67
+ if (params.package_registry !== undefined) {
68
+ formData.append('mcp_implementation[package_registry]', params.package_registry);
69
+ }
70
+ if (params.package_name !== undefined) {
71
+ formData.append('mcp_implementation[package_name]', params.package_name);
72
+ }
73
+ // Flags
74
+ if (params.recommended !== undefined) {
75
+ formData.append('mcp_implementation[recommended]', params.recommended.toString());
76
+ }
77
+ // Date overrides
78
+ if (params.created_on_override !== undefined) {
79
+ formData.append('mcp_implementation[created_on_override]', params.created_on_override);
80
+ }
81
+ // Tags
82
+ if (params.tags !== undefined) {
83
+ if (params.tags.length > 0) {
84
+ params.tags.forEach((tagSlug, index) => {
85
+ formData.append(`mcp_implementation[tags][${index}]`, tagSlug);
86
+ });
87
+ }
88
+ else {
89
+ // Empty array explicitly provided - send empty array marker to Rails
90
+ formData.append('mcp_implementation[tags]', '[]');
91
+ }
92
+ }
66
93
  // Internal notes
67
94
  if (params.internal_notes !== undefined) {
68
95
  formData.append('mcp_implementation[internal_notes]', params.internal_notes);
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Maps a Rails implementation response to a unified MCP server representation.
3
+ * Returns null if the implementation doesn't have an associated MCPServer.
4
+ */
5
+ export function mapToUnifiedServer(impl) {
6
+ // Only map implementations that have an associated MCPServer
7
+ if (!impl.mcp_server || !impl.mcp_server_id) {
8
+ return null;
9
+ }
10
+ const mcpServer = impl.mcp_server;
11
+ return {
12
+ // Core identification
13
+ id: mcpServer.id,
14
+ slug: mcpServer.slug,
15
+ implementation_id: impl.id,
16
+ // Basic info
17
+ name: impl.name,
18
+ short_description: impl.short_description,
19
+ description: impl.description,
20
+ status: impl.status,
21
+ classification: impl.classification,
22
+ implementation_language: impl.implementation_language,
23
+ url: impl.marketing_url,
24
+ // Provider info
25
+ provider: impl.provider_id || impl.provider_name
26
+ ? {
27
+ id: impl.provider_id,
28
+ name: impl.provider_name,
29
+ slug: impl.provider_slug,
30
+ url: impl.provider_url,
31
+ }
32
+ : undefined,
33
+ // Source code location
34
+ source_code: impl.github_owner || impl.github_repo
35
+ ? {
36
+ github_owner: impl.github_owner,
37
+ github_repo: impl.github_repo,
38
+ github_subfolder: impl.github_subfolder,
39
+ github_stars: impl.github_stars,
40
+ github_created_date: impl.github_created_date,
41
+ github_last_updated: impl.github_last_updated,
42
+ github_status: impl.github_status,
43
+ }
44
+ : undefined,
45
+ // Package registry info
46
+ package_registry: impl.package_registry,
47
+ package_name: impl.package_name,
48
+ // Flags
49
+ recommended: impl.recommended,
50
+ // Canonical URLs
51
+ canonical_urls: impl.canonical,
52
+ // Remote endpoints
53
+ remotes: mcpServer.remotes?.map((r) => ({
54
+ id: r.id,
55
+ display_name: r.display_name,
56
+ url_direct: r.url_direct,
57
+ url_setup: r.url_setup,
58
+ transport: r.transport,
59
+ host_platform: r.host_platform,
60
+ host_infrastructure: r.host_infrastructure,
61
+ authentication_method: r.authentication_method,
62
+ cost: r.cost,
63
+ status: r.status,
64
+ internal_notes: r.internal_notes,
65
+ })),
66
+ // Tags
67
+ tags: mcpServer.tags,
68
+ // Registry/download info
69
+ registry_package_id: mcpServer.registry_package_id,
70
+ registry_package_soft_verified: mcpServer.registry_package_soft_verified,
71
+ downloads_estimate_last_7_days: mcpServer.downloads_estimate_last_7_days,
72
+ downloads_estimate_last_30_days: mcpServer.downloads_estimate_last_30_days,
73
+ downloads_estimate_total: mcpServer.downloads_estimate_total,
74
+ // Internal notes
75
+ internal_notes: impl.internal_notes,
76
+ // Timestamps (use implementation timestamps as they're more relevant)
77
+ created_at: impl.created_at,
78
+ updated_at: impl.updated_at,
79
+ created_on_override: impl.created_on_override,
80
+ };
81
+ }
@@ -0,0 +1,90 @@
1
+ import { mapToUnifiedServer } from './unified-mcp-server-mapper.js';
2
+ /**
3
+ * Update a unified MCP server by its implementation ID.
4
+ * This abstracts the complexity of updating the MCPImplementation and MCPServer relationship.
5
+ */
6
+ export async function updateUnifiedMCPServer(apiKey, baseUrl, implementationId, params) {
7
+ // Transform unified params to implementation params
8
+ const implParams = {};
9
+ // Basic info
10
+ if (params.name !== undefined)
11
+ implParams.name = params.name;
12
+ if (params.short_description !== undefined)
13
+ implParams.short_description = params.short_description;
14
+ if (params.description !== undefined)
15
+ implParams.description = params.description;
16
+ if (params.status !== undefined)
17
+ implParams.status = params.status;
18
+ if (params.classification !== undefined)
19
+ implParams.classification = params.classification;
20
+ if (params.implementation_language !== undefined)
21
+ implParams.implementation_language = params.implementation_language;
22
+ if (params.url !== undefined)
23
+ implParams.url = params.url;
24
+ if (params.internal_notes !== undefined)
25
+ implParams.internal_notes = params.internal_notes;
26
+ // Provider handling
27
+ if (params.provider_id !== undefined)
28
+ implParams.provider_id = params.provider_id;
29
+ if (params.provider_name !== undefined)
30
+ implParams.provider_name = params.provider_name;
31
+ if (params.provider_slug !== undefined)
32
+ implParams.provider_slug = params.provider_slug;
33
+ if (params.provider_url !== undefined)
34
+ implParams.provider_url = params.provider_url;
35
+ // Source code location
36
+ if (params.source_code) {
37
+ if (params.source_code.github_owner !== undefined)
38
+ implParams.github_owner = params.source_code.github_owner;
39
+ if (params.source_code.github_repo !== undefined)
40
+ implParams.github_repo = params.source_code.github_repo;
41
+ if (params.source_code.github_subfolder !== undefined)
42
+ implParams.github_subfolder = params.source_code.github_subfolder;
43
+ }
44
+ // Package registry info
45
+ if (params.package_registry !== undefined)
46
+ implParams.package_registry = params.package_registry;
47
+ if (params.package_name !== undefined)
48
+ implParams.package_name = params.package_name;
49
+ // Flags
50
+ if (params.recommended !== undefined)
51
+ implParams.recommended = params.recommended;
52
+ // Date overrides
53
+ if (params.created_on_override !== undefined)
54
+ implParams.created_on_override = params.created_on_override;
55
+ // Tags
56
+ if (params.tags !== undefined)
57
+ implParams.tags = params.tags;
58
+ // Canonical URLs
59
+ if (params.canonical_urls !== undefined) {
60
+ implParams.canonical = params.canonical_urls;
61
+ }
62
+ // Remote endpoints
63
+ if (params.remotes !== undefined) {
64
+ implParams.remote = params.remotes.map((r) => ({
65
+ id: r.id,
66
+ display_name: r.display_name,
67
+ url_direct: r.url_direct,
68
+ url_setup: r.url_setup,
69
+ transport: r.transport,
70
+ host_platform: r.host_platform,
71
+ host_infrastructure: r.host_infrastructure,
72
+ authentication_method: r.authentication_method,
73
+ cost: r.cost,
74
+ status: r.status,
75
+ internal_notes: r.internal_notes,
76
+ }));
77
+ }
78
+ // Use the existing save-mcp-implementation endpoint
79
+ const { saveMCPImplementation } = await import('./save-mcp-implementation.js');
80
+ const result = await saveMCPImplementation(apiKey, baseUrl, implementationId, implParams);
81
+ // Map the result back to a unified server
82
+ const unified = mapToUnifiedServer(result);
83
+ if (!unified) {
84
+ // If the result doesn't have mcp_server, the implementation isn't linked to an MCPServer.
85
+ // This is unexpected for the mcp_servers tool group - throw an error to surface the issue.
86
+ throw new Error(`Implementation ${result.id} (${result.name}) is not linked to an MCPServer. ` +
87
+ `Use the implementations tools instead to manage this record.`);
88
+ }
89
+ return unified;
90
+ }
@@ -738,5 +738,51 @@ export function createMockPulseMCPAdminClient(mockData) {
738
738
  async deleteMcpJson() {
739
739
  return { success: true, message: 'MCP JSON deleted' };
740
740
  },
741
+ // Unified MCP Server methods (stub implementations)
742
+ async getUnifiedMCPServers() {
743
+ return {
744
+ servers: [],
745
+ pagination: { current_page: 1, total_pages: 1, total_count: 0 },
746
+ };
747
+ },
748
+ async getUnifiedMCPServer(slug) {
749
+ return {
750
+ id: 1,
751
+ slug,
752
+ implementation_id: 1,
753
+ name: `Test Server (${slug})`,
754
+ status: 'live',
755
+ classification: 'community',
756
+ created_at: new Date().toISOString(),
757
+ updated_at: new Date().toISOString(),
758
+ };
759
+ },
760
+ async updateUnifiedMCPServer(implementationId, params) {
761
+ return {
762
+ id: 1,
763
+ slug: 'test-server',
764
+ implementation_id: implementationId,
765
+ name: params.name || 'Updated Server',
766
+ short_description: params.short_description,
767
+ description: params.description,
768
+ status: params.status || 'live',
769
+ classification: params.classification || 'community',
770
+ implementation_language: params.implementation_language,
771
+ url: params.url,
772
+ provider: params.provider_name
773
+ ? {
774
+ name: params.provider_name,
775
+ slug: params.provider_slug,
776
+ url: params.provider_url,
777
+ }
778
+ : undefined,
779
+ source_code: params.source_code,
780
+ canonical_urls: params.canonical_urls,
781
+ remotes: params.remotes,
782
+ internal_notes: params.internal_notes,
783
+ created_at: new Date().toISOString(),
784
+ updated_at: new Date().toISOString(),
785
+ };
786
+ },
741
787
  };
742
788
  }
@@ -173,6 +173,19 @@ export class PulseMCPAdminClient {
173
173
  const { deleteMcpJson } = await import('./pulsemcp-admin-client/lib/delete-mcp-json.js');
174
174
  return deleteMcpJson(this.apiKey, this.baseUrl, id);
175
175
  }
176
+ // Unified MCP Server methods (abstracted interface)
177
+ async getUnifiedMCPServers(params) {
178
+ const { getUnifiedMCPServers } = await import('./pulsemcp-admin-client/lib/get-unified-mcp-servers.js');
179
+ return getUnifiedMCPServers(this.apiKey, this.baseUrl, params);
180
+ }
181
+ async getUnifiedMCPServer(slug) {
182
+ const { getUnifiedMCPServer } = await import('./pulsemcp-admin-client/lib/get-unified-mcp-server.js');
183
+ return getUnifiedMCPServer(this.apiKey, this.baseUrl, slug);
184
+ }
185
+ async updateUnifiedMCPServer(implementationId, params) {
186
+ const { updateUnifiedMCPServer } = await import('./pulsemcp-admin-client/lib/update-unified-mcp-server.js');
187
+ return updateUnifiedMCPServer(this.apiKey, this.baseUrl, implementationId, params);
188
+ }
176
189
  }
177
190
  export function createMCPServer() {
178
191
  const server = new Server({