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.
- package/README.md +78 -34
- package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +93 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +51 -0
- package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +27 -0
- package/build/shared/src/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +81 -0
- package/build/shared/src/pulsemcp-admin-client/lib/update-unified-mcp-server.js +90 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +46 -0
- package/build/shared/src/server.js +13 -0
- package/build/shared/src/tools/get-mcp-server.js +246 -0
- package/build/shared/src/tools/list-mcp-servers.js +146 -0
- package/build/shared/src/tools/update-mcp-server.js +340 -0
- package/build/shared/src/tools.js +15 -0
- package/package.json +1 -1
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +93 -0
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-servers.d.ts +9 -0
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-servers.js +51 -0
- package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +27 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.d.ts +72 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +81 -0
- package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.js +90 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +46 -0
- package/shared/server.d.ts +19 -1
- package/shared/server.js +13 -0
- package/shared/tools/get-mcp-server.d.ts +30 -0
- package/shared/tools/get-mcp-server.js +246 -0
- package/shared/tools/list-mcp-servers.d.ts +50 -0
- package/shared/tools/list-mcp-servers.js +146 -0
- package/shared/tools/update-mcp-server.d.ts +199 -0
- package/shared/tools/update-mcp-server.js +340 -0
- package/shared/tools.d.ts +4 -1
- package/shared/tools.js +15 -0
- 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
|
|
44
|
-
| -------------------------------------- |
|
|
45
|
-
| `get_newsletter_posts` | newsletter
|
|
46
|
-
| `get_newsletter_post` | newsletter
|
|
47
|
-
| `draft_newsletter_post` | newsletter
|
|
48
|
-
| `update_newsletter_post` | newsletter
|
|
49
|
-
| `upload_image` | newsletter
|
|
50
|
-
| `get_authors` | newsletter
|
|
51
|
-
| `search_mcp_implementations` | server_queue
|
|
52
|
-
| `get_draft_mcp_implementations` | server_queue
|
|
53
|
-
| `find_providers` | server_queue
|
|
54
|
-
| `save_mcp_implementation` | server_queue
|
|
55
|
-
| `send_impl_posted_notif` | server_queue
|
|
56
|
-
| `get_official_mirror_queue_items` | official_queue
|
|
57
|
-
| `get_official_mirror_queue_item` | official_queue
|
|
58
|
-
| `approve_official_mirror_queue_item` | official_queue
|
|
59
|
-
| `approve_mirror_no_modify` | official_queue
|
|
60
|
-
| `reject_official_mirror_queue_item` | official_queue
|
|
61
|
-
| `add_official_mirror_to_regular_queue` | official_queue
|
|
62
|
-
| `unlink_official_mirror_queue_item` | official_queue
|
|
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
|
|
74
|
-
|
|
|
75
|
-
| `newsletter`
|
|
76
|
-
| `newsletter_readonly`
|
|
77
|
-
| `server_queue`
|
|
78
|
-
| `server_queue_readonly`
|
|
79
|
-
| `official_queue`
|
|
80
|
-
| `official_queue_readonly`
|
|
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({
|