pulsemcp-cms-admin-mcp-server 0.9.17 → 0.9.24
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 +93 -74
- package/build/index.js +53 -1
- package/build/shared/src/elicitation-config.js +69 -0
- package/build/shared/src/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
- package/build/shared/src/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
- package/build/shared/src/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
- package/build/shared/src/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
- package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
- package/build/shared/src/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
- package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
- package/build/shared/src/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
- package/build/shared/src/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +29 -0
- package/build/shared/src/server.js +16 -0
- package/build/shared/src/tools/add-servers-to-tenant.js +133 -0
- package/build/shared/src/tools/delete-api-key.js +119 -0
- package/build/shared/src/tools/delete-tenant.js +129 -0
- package/build/shared/src/tools/get-mcp-server.js +26 -8
- package/build/shared/src/tools/list-mcp-servers.js +3 -0
- package/build/shared/src/tools/list-tenant-servers.js +89 -0
- package/build/shared/src/tools/remove-servers-from-tenant.js +92 -0
- package/build/shared/src/tools/revoke-api-key.js +119 -0
- package/build/shared/src/tools/save-mcp-implementation.js +89 -2
- package/build/shared/src/tools/update-mcp-server.js +19 -0
- package/build/shared/src/tools.js +22 -1
- package/node_modules/@pulsemcp/mcp-elicitation/build/config.d.ts +15 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/config.js +41 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.d.ts +24 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.js +175 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/index.d.ts +3 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/index.js +2 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/types.d.ts +114 -0
- package/node_modules/@pulsemcp/mcp-elicitation/build/types.js +1 -0
- package/node_modules/@pulsemcp/mcp-elicitation/package.json +28 -0
- package/package.json +7 -1
- package/shared/elicitation-config.d.ts +61 -0
- package/shared/elicitation-config.js +69 -0
- package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
- package/shared/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
- package/shared/pulsemcp-admin-client/lib/delete-api-key.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
- package/shared/pulsemcp-admin-client/lib/delete-tenant.d.ts +3 -0
- package/shared/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
- package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
- package/shared/pulsemcp-admin-client/lib/list-tenant-servers.d.ts +7 -0
- package/shared/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
- package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.d.ts +2 -0
- package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
- package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +29 -0
- package/shared/server.d.ts +17 -1
- package/shared/server.js +16 -0
- package/shared/tools/add-servers-to-tenant.d.ts +57 -0
- package/shared/tools/add-servers-to-tenant.js +133 -0
- package/shared/tools/delete-api-key.d.ts +30 -0
- package/shared/tools/delete-api-key.js +119 -0
- package/shared/tools/delete-tenant.d.ts +36 -0
- package/shared/tools/delete-tenant.js +129 -0
- package/shared/tools/get-mcp-server.js +26 -8
- package/shared/tools/list-mcp-servers.js +3 -0
- package/shared/tools/list-tenant-servers.d.ts +45 -0
- package/shared/tools/list-tenant-servers.js +89 -0
- package/shared/tools/remove-servers-from-tenant.d.ts +42 -0
- package/shared/tools/remove-servers-from-tenant.js +92 -0
- package/shared/tools/revoke-api-key.d.ts +30 -0
- package/shared/tools/revoke-api-key.js +119 -0
- package/shared/tools/save-mcp-implementation.d.ts +9 -1
- package/shared/tools/save-mcp-implementation.js +89 -2
- package/shared/tools/update-mcp-server.d.ts +6 -0
- package/shared/tools/update-mcp-server.js +19 -0
- package/shared/tools.d.ts +5 -3
- package/shared/tools.js +22 -1
- package/shared/types.d.ts +83 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { adminFetch } from './admin-fetch.js';
|
|
2
|
+
function mapTenantServer(row) {
|
|
3
|
+
return {
|
|
4
|
+
id: row.id,
|
|
5
|
+
tenant_id: row.tenant_id,
|
|
6
|
+
mcp_server_id: row.mcp_server_id,
|
|
7
|
+
mcp_server_slug: row.mcp_server_slug,
|
|
8
|
+
status: row.status,
|
|
9
|
+
server_json_selection: row.server_json_selection,
|
|
10
|
+
first_touched_at: row.first_touched_at,
|
|
11
|
+
touched: row.touched,
|
|
12
|
+
created_at: row.created_at,
|
|
13
|
+
updated_at: row.updated_at,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function listTenantServers(apiKey, baseUrl, idOrSlug, params) {
|
|
17
|
+
const url = new URL(`/api/tenants/${idOrSlug}/servers`, baseUrl);
|
|
18
|
+
if (params?.status) {
|
|
19
|
+
url.searchParams.append('status', params.status);
|
|
20
|
+
}
|
|
21
|
+
if (params?.limit !== undefined) {
|
|
22
|
+
url.searchParams.append('limit', params.limit.toString());
|
|
23
|
+
}
|
|
24
|
+
if (params?.offset !== undefined) {
|
|
25
|
+
url.searchParams.append('offset', params.offset.toString());
|
|
26
|
+
}
|
|
27
|
+
const response = await adminFetch(url.toString(), {
|
|
28
|
+
method: 'GET',
|
|
29
|
+
headers: {
|
|
30
|
+
'X-API-Key': apiKey,
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
if (response.status === 401) {
|
|
36
|
+
throw new Error('Invalid API key');
|
|
37
|
+
}
|
|
38
|
+
if (response.status === 403) {
|
|
39
|
+
throw new Error('User lacks admin privileges');
|
|
40
|
+
}
|
|
41
|
+
if (response.status === 404) {
|
|
42
|
+
throw new Error(`Tenant with ID/slug ${idOrSlug} not found`);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Failed to list tenant servers: ${response.status} ${response.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
const data = (await response.json());
|
|
47
|
+
return {
|
|
48
|
+
data: data.data.map(mapTenantServer),
|
|
49
|
+
pagination: {
|
|
50
|
+
current_page: data.meta.current_page,
|
|
51
|
+
total_pages: data.meta.total_pages,
|
|
52
|
+
total_count: data.meta.total_count,
|
|
53
|
+
has_next: data.meta.has_next,
|
|
54
|
+
limit: data.meta.limit,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -98,6 +98,14 @@ export async function saveMCPImplementation(apiKey, baseUrl, id, params) {
|
|
|
98
98
|
if (params.internal_notes !== undefined) {
|
|
99
99
|
formData.append('mcp_implementation[internal_notes]', params.internal_notes);
|
|
100
100
|
}
|
|
101
|
+
// Owner tenant linking. Slug wins over id on the Rails side when both are sent.
|
|
102
|
+
// null/empty value clears the link (Rails uses .presence semantics).
|
|
103
|
+
if (params.owner_tenant_slug !== undefined) {
|
|
104
|
+
formData.append('mcp_implementation[owner_tenant_slug]', params.owner_tenant_slug === null ? '' : params.owner_tenant_slug);
|
|
105
|
+
}
|
|
106
|
+
if (params.owner_tenant_id !== undefined) {
|
|
107
|
+
formData.append('mcp_implementation[owner_tenant_id]', params.owner_tenant_id === null ? '' : params.owner_tenant_id.toString());
|
|
108
|
+
}
|
|
101
109
|
// Remote endpoints
|
|
102
110
|
// Rails expects nested attributes to use the _attributes suffix for has_many associations
|
|
103
111
|
if (params.remote !== undefined) {
|
|
@@ -47,6 +47,8 @@ export interface RailsImplementation {
|
|
|
47
47
|
mcp_server_remotes_count?: number;
|
|
48
48
|
recommended?: boolean;
|
|
49
49
|
verified_no_remote_canonicals?: boolean;
|
|
50
|
+
owner_tenant_id?: number | null;
|
|
51
|
+
owner_tenant_slug?: string | null;
|
|
50
52
|
tags?: MCPServerTag[];
|
|
51
53
|
remotes?: MCPServerRemote[];
|
|
52
54
|
created_at?: string;
|
|
@@ -74,6 +74,9 @@ export function mapToUnifiedServer(impl) {
|
|
|
74
74
|
downloads_estimate_total: mcpServer.downloads_estimate_total,
|
|
75
75
|
// Internal notes
|
|
76
76
|
internal_notes: impl.internal_notes,
|
|
77
|
+
// Owner tenant
|
|
78
|
+
owner_tenant_id: mcpServer.owner_tenant_id,
|
|
79
|
+
owner_tenant_slug: mcpServer.owner_tenant_slug,
|
|
77
80
|
// Timestamps (use implementation timestamps as they're more relevant)
|
|
78
81
|
created_at: impl.created_at,
|
|
79
82
|
updated_at: impl.updated_at,
|
|
@@ -54,6 +54,20 @@ export async function updateUnifiedMCPServer(apiKey, baseUrl, implementationId,
|
|
|
54
54
|
// Date overrides
|
|
55
55
|
if (params.created_on_override !== undefined)
|
|
56
56
|
implParams.created_on_override = params.created_on_override;
|
|
57
|
+
// Owner tenant: route to slug or id depending on the value type.
|
|
58
|
+
// String → slug, number → id, null → clear (sent as empty owner_tenant_id).
|
|
59
|
+
if (params.owner_tenant !== undefined) {
|
|
60
|
+
if (typeof params.owner_tenant === 'string') {
|
|
61
|
+
implParams.owner_tenant_slug = params.owner_tenant;
|
|
62
|
+
}
|
|
63
|
+
else if (typeof params.owner_tenant === 'number') {
|
|
64
|
+
implParams.owner_tenant_id = params.owner_tenant;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// null — clear the link
|
|
68
|
+
implParams.owner_tenant_id = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
57
71
|
// Tags
|
|
58
72
|
if (params.tags !== undefined)
|
|
59
73
|
implParams.tags = params.tags;
|
|
@@ -1045,6 +1045,35 @@ export function createMockPulseMCPAdminClient(mockData) {
|
|
|
1045
1045
|
created_at: '2024-01-01T00:00:00Z',
|
|
1046
1046
|
};
|
|
1047
1047
|
},
|
|
1048
|
+
async deleteTenant() {
|
|
1049
|
+
return { success: true, message: 'Tenant deleted' };
|
|
1050
|
+
},
|
|
1051
|
+
async deleteApiKey() {
|
|
1052
|
+
return { success: true, message: 'API key revoked' };
|
|
1053
|
+
},
|
|
1054
|
+
async listTenantServers() {
|
|
1055
|
+
return {
|
|
1056
|
+
data: [],
|
|
1057
|
+
pagination: {
|
|
1058
|
+
current_page: 1,
|
|
1059
|
+
total_pages: 0,
|
|
1060
|
+
total_count: 0,
|
|
1061
|
+
has_next: false,
|
|
1062
|
+
limit: 30,
|
|
1063
|
+
},
|
|
1064
|
+
};
|
|
1065
|
+
},
|
|
1066
|
+
async bulkUpdateTenantServers(idOrSlug) {
|
|
1067
|
+
return {
|
|
1068
|
+
status: 'success',
|
|
1069
|
+
tenant: { id: typeof idOrSlug === 'number' ? idOrSlug : 1, slug: String(idOrSlug) },
|
|
1070
|
+
added: [],
|
|
1071
|
+
removed: [],
|
|
1072
|
+
restored: [],
|
|
1073
|
+
skipped: [],
|
|
1074
|
+
unresolved_identifiers: [],
|
|
1075
|
+
};
|
|
1076
|
+
},
|
|
1048
1077
|
async recacheMCPServer(slug) {
|
|
1049
1078
|
return {
|
|
1050
1079
|
message: `Cache successfully refreshed for ${slug}.`,
|
package/shared/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import type { Post, PostsResponse, CreatePostParams, UpdatePostParams, ImageUploadResponse, Author, AuthorsResponse, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, SaveMCPImplementationParams, CreateMCPImplementationParams, Provider, ProvidersResponse, OfficialMirrorQueueStatus, OfficialMirrorQueueResponse, OfficialMirrorQueueItemDetail, OfficialMirrorQueueActionResponse, UnofficialMirror, UnofficialMirrorsResponse, CreateUnofficialMirrorParams, UpdateUnofficialMirrorParams, OfficialMirrorRest, OfficialMirrorsResponse, Tenant, TenantsResponse, McpJson, McpJsonsResponse, CreateMcpJsonParams, UpdateMcpJsonParams, UnifiedMCPServer, UnifiedMCPServersResponse, UpdateUnifiedMCPServerParams, Redirect, RedirectsResponse, RedirectStatus, CreateRedirectParams, UpdateRedirectParams, GoodJob, GoodJobsResponse, GoodJobStatus, GoodJobCronSchedule, GoodJobProcess, GoodJobStatistics, GoodJobActionResponse, GoodJobCleanupResponse, ProctorRunExamParams, ProctorRunExamResponse, ProctorSaveResultsParams, ProctorSaveResultsResponse, ProctorRunsResponse, GetProctorRunsParams, ProctorMetadataResponse, DiscoveredUrlsResponse, MarkDiscoveredUrlProcessedParams, MarkDiscoveredUrlProcessedResponse, DiscoveredUrlStats, MozMetricsResponse, MozBacklinksResponse, MozStoredMetricsResponse, CreateTenantParams, ApiKey, CreateApiKeyParams, RecacheMCPServerResponse } from './types.js';
|
|
2
|
+
import type { Post, PostsResponse, CreatePostParams, UpdatePostParams, ImageUploadResponse, Author, AuthorsResponse, MCPServer, MCPClient, MCPImplementation, MCPImplementationsResponse, SaveMCPImplementationParams, CreateMCPImplementationParams, Provider, ProvidersResponse, OfficialMirrorQueueStatus, OfficialMirrorQueueResponse, OfficialMirrorQueueItemDetail, OfficialMirrorQueueActionResponse, UnofficialMirror, UnofficialMirrorsResponse, CreateUnofficialMirrorParams, UpdateUnofficialMirrorParams, OfficialMirrorRest, OfficialMirrorsResponse, Tenant, TenantsResponse, ListTenantServersResponse, BulkUpdateTenantServersParams, BulkUpdateTenantServersResponse, McpJson, McpJsonsResponse, CreateMcpJsonParams, UpdateMcpJsonParams, UnifiedMCPServer, UnifiedMCPServersResponse, UpdateUnifiedMCPServerParams, Redirect, RedirectsResponse, RedirectStatus, CreateRedirectParams, UpdateRedirectParams, GoodJob, GoodJobsResponse, GoodJobStatus, GoodJobCronSchedule, GoodJobProcess, GoodJobStatistics, GoodJobActionResponse, GoodJobCleanupResponse, ProctorRunExamParams, ProctorRunExamResponse, ProctorSaveResultsParams, ProctorSaveResultsResponse, ProctorRunsResponse, GetProctorRunsParams, ProctorMetadataResponse, DiscoveredUrlsResponse, MarkDiscoveredUrlProcessedParams, MarkDiscoveredUrlProcessedResponse, DiscoveredUrlStats, MozMetricsResponse, MozBacklinksResponse, MozStoredMetricsResponse, CreateTenantParams, ApiKey, CreateApiKeyParams, DeleteTenantParams, DeleteTenantResponse, DeleteApiKeyResponse, RecacheMCPServerResponse } from './types.js';
|
|
3
3
|
export interface IPulseMCPAdminClient {
|
|
4
4
|
getPosts(params?: {
|
|
5
5
|
search?: string;
|
|
@@ -104,6 +104,14 @@ export interface IPulseMCPAdminClient {
|
|
|
104
104
|
getTenant(idOrSlug: number | string): Promise<Tenant>;
|
|
105
105
|
createTenant(params: CreateTenantParams): Promise<Tenant>;
|
|
106
106
|
createApiKey(params: CreateApiKeyParams): Promise<ApiKey>;
|
|
107
|
+
deleteTenant(params: DeleteTenantParams): Promise<DeleteTenantResponse>;
|
|
108
|
+
deleteApiKey(id: number): Promise<DeleteApiKeyResponse>;
|
|
109
|
+
listTenantServers(idOrSlug: number | string, params?: {
|
|
110
|
+
status?: 'active' | 'deleted' | 'all';
|
|
111
|
+
limit?: number;
|
|
112
|
+
offset?: number;
|
|
113
|
+
}): Promise<ListTenantServersResponse>;
|
|
114
|
+
bulkUpdateTenantServers(idOrSlug: number | string, params: BulkUpdateTenantServersParams): Promise<BulkUpdateTenantServersResponse>;
|
|
107
115
|
getMcpJsons(params?: {
|
|
108
116
|
unofficial_mirror_id?: number;
|
|
109
117
|
q?: string;
|
|
@@ -295,6 +303,14 @@ export declare class PulseMCPAdminClient implements IPulseMCPAdminClient {
|
|
|
295
303
|
getTenant(idOrSlug: number | string): Promise<Tenant>;
|
|
296
304
|
createTenant(params: CreateTenantParams): Promise<Tenant>;
|
|
297
305
|
createApiKey(params: CreateApiKeyParams): Promise<ApiKey>;
|
|
306
|
+
deleteTenant(params: DeleteTenantParams): Promise<DeleteTenantResponse>;
|
|
307
|
+
deleteApiKey(id: number): Promise<DeleteApiKeyResponse>;
|
|
308
|
+
listTenantServers(idOrSlug: number | string, params?: {
|
|
309
|
+
status?: 'active' | 'deleted' | 'all';
|
|
310
|
+
limit?: number;
|
|
311
|
+
offset?: number;
|
|
312
|
+
}): Promise<ListTenantServersResponse>;
|
|
313
|
+
bulkUpdateTenantServers(idOrSlug: number | string, params: BulkUpdateTenantServersParams): Promise<BulkUpdateTenantServersResponse>;
|
|
298
314
|
getMcpJsons(params?: {
|
|
299
315
|
unofficial_mirror_id?: number;
|
|
300
316
|
q?: string;
|
package/shared/server.js
CHANGED
|
@@ -74,6 +74,10 @@ import { getMozStoredMetrics } from './pulsemcp-admin-client/lib/get-moz-stored-
|
|
|
74
74
|
import { recacheMCPServer } from './pulsemcp-admin-client/lib/recache-mcp-server.js';
|
|
75
75
|
import { createTenant } from './pulsemcp-admin-client/lib/create-tenant.js';
|
|
76
76
|
import { createApiKey } from './pulsemcp-admin-client/lib/create-api-key.js';
|
|
77
|
+
import { deleteTenant } from './pulsemcp-admin-client/lib/delete-tenant.js';
|
|
78
|
+
import { deleteApiKey } from './pulsemcp-admin-client/lib/delete-api-key.js';
|
|
79
|
+
import { listTenantServers } from './pulsemcp-admin-client/lib/list-tenant-servers.js';
|
|
80
|
+
import { bulkUpdateTenantServers } from './pulsemcp-admin-client/lib/bulk-update-tenant-servers.js';
|
|
77
81
|
// PulseMCP Admin API client implementation
|
|
78
82
|
export class PulseMCPAdminClient {
|
|
79
83
|
apiKey;
|
|
@@ -200,6 +204,18 @@ export class PulseMCPAdminClient {
|
|
|
200
204
|
async createApiKey(params) {
|
|
201
205
|
return createApiKey(this.apiKey, this.baseUrl, params);
|
|
202
206
|
}
|
|
207
|
+
async deleteTenant(params) {
|
|
208
|
+
return deleteTenant(this.apiKey, this.baseUrl, params);
|
|
209
|
+
}
|
|
210
|
+
async deleteApiKey(id) {
|
|
211
|
+
return deleteApiKey(this.apiKey, this.baseUrl, id);
|
|
212
|
+
}
|
|
213
|
+
async listTenantServers(idOrSlug, params) {
|
|
214
|
+
return listTenantServers(this.apiKey, this.baseUrl, idOrSlug, params);
|
|
215
|
+
}
|
|
216
|
+
async bulkUpdateTenantServers(idOrSlug, params) {
|
|
217
|
+
return bulkUpdateTenantServers(this.apiKey, this.baseUrl, idOrSlug, params);
|
|
218
|
+
}
|
|
203
219
|
// MCP JSON REST API methods
|
|
204
220
|
async getMcpJsons(params) {
|
|
205
221
|
return getMcpJsons(this.apiKey, this.baseUrl, params);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function addServersToTenant(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
tenant: {
|
|
10
|
+
oneOf: {
|
|
11
|
+
type: string;
|
|
12
|
+
}[];
|
|
13
|
+
description: "Tenant ID (number) or slug (string) to add servers to.";
|
|
14
|
+
};
|
|
15
|
+
add_server_identifiers: {
|
|
16
|
+
type: string;
|
|
17
|
+
items: {
|
|
18
|
+
oneOf: {
|
|
19
|
+
type: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
description: "Array of MCP server IDs (numbers) or slugs (strings) to add to the tenant's recommendation list. Servers without an unofficial mirror are silently skipped (returned in `skipped` with reason \"no_unofficial_mirror\"). Already-active associations are skipped (reason \"already_active\").";
|
|
23
|
+
};
|
|
24
|
+
remove_server_identifiers: {
|
|
25
|
+
type: string;
|
|
26
|
+
items: {
|
|
27
|
+
oneOf: {
|
|
28
|
+
type: string;
|
|
29
|
+
}[];
|
|
30
|
+
};
|
|
31
|
+
description: "Optional array of MCP server IDs or slugs to remove from the tenant in the same call. Touched associations are soft-deleted; untouched associations are hard-deleted.";
|
|
32
|
+
};
|
|
33
|
+
restore_association_ids: {
|
|
34
|
+
type: string;
|
|
35
|
+
items: {
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
description: "Optional array of TenantsMcpServer association IDs to restore from a soft-deleted state. Use list_tenant_servers with status=\"deleted\" to find these IDs.";
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
required: string[];
|
|
42
|
+
};
|
|
43
|
+
handler: (args: unknown) => Promise<{
|
|
44
|
+
content: {
|
|
45
|
+
type: string;
|
|
46
|
+
text: string;
|
|
47
|
+
}[];
|
|
48
|
+
isError?: undefined;
|
|
49
|
+
} | {
|
|
50
|
+
content: {
|
|
51
|
+
type: string;
|
|
52
|
+
text: string;
|
|
53
|
+
}[];
|
|
54
|
+
isError: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=add-servers-to-tenant.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const PARAM_DESCRIPTIONS = {
|
|
3
|
+
tenant: 'Tenant ID (number) or slug (string) to add servers to.',
|
|
4
|
+
add_server_identifiers: 'Array of MCP server IDs (numbers) or slugs (strings) to add to the tenant\'s recommendation list. Servers without an unofficial mirror are silently skipped (returned in `skipped` with reason "no_unofficial_mirror"). Already-active associations are skipped (reason "already_active").',
|
|
5
|
+
remove_server_identifiers: 'Optional array of MCP server IDs or slugs to remove from the tenant in the same call. Touched associations are soft-deleted; untouched associations are hard-deleted.',
|
|
6
|
+
restore_association_ids: 'Optional array of TenantsMcpServer association IDs to restore from a soft-deleted state. Use list_tenant_servers with status="deleted" to find these IDs.',
|
|
7
|
+
};
|
|
8
|
+
const AddServersToTenantSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
tenant: z.union([z.number(), z.string()]).describe(PARAM_DESCRIPTIONS.tenant),
|
|
11
|
+
add_server_identifiers: z
|
|
12
|
+
.array(z.union([z.number(), z.string()]))
|
|
13
|
+
.optional()
|
|
14
|
+
.describe(PARAM_DESCRIPTIONS.add_server_identifiers),
|
|
15
|
+
remove_server_identifiers: z
|
|
16
|
+
.array(z.union([z.number(), z.string()]))
|
|
17
|
+
.optional()
|
|
18
|
+
.describe(PARAM_DESCRIPTIONS.remove_server_identifiers),
|
|
19
|
+
restore_association_ids: z
|
|
20
|
+
.array(z.number())
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(PARAM_DESCRIPTIONS.restore_association_ids),
|
|
23
|
+
})
|
|
24
|
+
.refine((val) => (val.add_server_identifiers && val.add_server_identifiers.length > 0) ||
|
|
25
|
+
(val.remove_server_identifiers && val.remove_server_identifiers.length > 0) ||
|
|
26
|
+
(val.restore_association_ids && val.restore_association_ids.length > 0), {
|
|
27
|
+
message: 'Must provide at least one of add_server_identifiers, remove_server_identifiers, or restore_association_ids',
|
|
28
|
+
});
|
|
29
|
+
export function addServersToTenant(_server, clientFactory) {
|
|
30
|
+
return {
|
|
31
|
+
name: 'add_servers_to_tenant',
|
|
32
|
+
description: `Add MCP servers to a tenant's recommendation list. Optionally remove and restore associations in the same transactional call.
|
|
33
|
+
|
|
34
|
+
Servers are added with server_json_selection: "unofficial" — they always pin to the unofficial mirror's server.json. A server without an unofficial mirror cannot be added and will be reported in "skipped" with reason "no_unofficial_mirror".
|
|
35
|
+
|
|
36
|
+
This tool only changes the tenant's recommendation list (TenantsMcpServer rows). The underlying MCP server's public listing is NOT affected — public servers stay public and continue to appear in the public directory.
|
|
37
|
+
|
|
38
|
+
The response includes:
|
|
39
|
+
- added: servers newly attached to the tenant (outcome "created" or "restored_from_deleted")
|
|
40
|
+
- removed: servers detached (outcome "hard_deleted" if untouched, "soft_deleted" if the row had been customized)
|
|
41
|
+
- restored: associations restored from soft-deleted state via restore_association_ids
|
|
42
|
+
- skipped: identifiers that resolved to a server but were skipped with a reason
|
|
43
|
+
- unresolved_identifiers: identifiers that didn't match any existing MCP server
|
|
44
|
+
|
|
45
|
+
Use cases:
|
|
46
|
+
- Curate a tenant's recommended-servers list
|
|
47
|
+
- Bulk-add servers by slug or ID in one call
|
|
48
|
+
- Combine adds and removes atomically (e.g., swap one server for another)`,
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
tenant: {
|
|
53
|
+
oneOf: [{ type: 'number' }, { type: 'string' }],
|
|
54
|
+
description: PARAM_DESCRIPTIONS.tenant,
|
|
55
|
+
},
|
|
56
|
+
add_server_identifiers: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: { oneOf: [{ type: 'number' }, { type: 'string' }] },
|
|
59
|
+
description: PARAM_DESCRIPTIONS.add_server_identifiers,
|
|
60
|
+
},
|
|
61
|
+
remove_server_identifiers: {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: { oneOf: [{ type: 'number' }, { type: 'string' }] },
|
|
64
|
+
description: PARAM_DESCRIPTIONS.remove_server_identifiers,
|
|
65
|
+
},
|
|
66
|
+
restore_association_ids: {
|
|
67
|
+
type: 'array',
|
|
68
|
+
items: { type: 'number' },
|
|
69
|
+
description: PARAM_DESCRIPTIONS.restore_association_ids,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['tenant'],
|
|
73
|
+
},
|
|
74
|
+
handler: async (args) => {
|
|
75
|
+
try {
|
|
76
|
+
const validatedArgs = AddServersToTenantSchema.parse(args);
|
|
77
|
+
const client = clientFactory();
|
|
78
|
+
const result = await client.bulkUpdateTenantServers(validatedArgs.tenant, {
|
|
79
|
+
add_server_identifiers: validatedArgs.add_server_identifiers,
|
|
80
|
+
remove_server_identifiers: validatedArgs.remove_server_identifiers,
|
|
81
|
+
restore_association_ids: validatedArgs.restore_association_ids,
|
|
82
|
+
});
|
|
83
|
+
let content = `**Tenant servers updated** (tenant=${result.tenant.slug}, id=${result.tenant.id})\n\n`;
|
|
84
|
+
content += `- Added: ${result.added.length}\n`;
|
|
85
|
+
content += `- Removed: ${result.removed.length}\n`;
|
|
86
|
+
content += `- Restored: ${result.restored.length}\n`;
|
|
87
|
+
content += `- Skipped: ${result.skipped.length}\n`;
|
|
88
|
+
content += `- Unresolved identifiers: ${result.unresolved_identifiers.length}\n`;
|
|
89
|
+
if (result.added.length > 0) {
|
|
90
|
+
content += `\n**Added:**\n`;
|
|
91
|
+
for (const item of result.added) {
|
|
92
|
+
content += `- ${item.mcp_server_slug} (server_id=${item.mcp_server_id}, association_id=${item.association_id}, outcome=${item.outcome})\n`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (result.removed.length > 0) {
|
|
96
|
+
content += `\n**Removed:**\n`;
|
|
97
|
+
for (const item of result.removed) {
|
|
98
|
+
const assocPart = item.association_id != null ? `, association_id=${item.association_id}` : '';
|
|
99
|
+
content += `- ${item.mcp_server_slug} (server_id=${item.mcp_server_id}${assocPart}, outcome=${item.outcome})\n`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (result.restored.length > 0) {
|
|
103
|
+
content += `\n**Restored:**\n`;
|
|
104
|
+
for (const item of result.restored) {
|
|
105
|
+
content += `- ${item.mcp_server_slug} (server_id=${item.mcp_server_id}, association_id=${item.association_id})\n`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (result.skipped.length > 0) {
|
|
109
|
+
content += `\n**Skipped:**\n`;
|
|
110
|
+
for (const item of result.skipped) {
|
|
111
|
+
const label = item.mcp_server_slug || item.mcp_server_id || item.association_id;
|
|
112
|
+
content += `- ${label} — reason: ${item.reason}\n`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (result.unresolved_identifiers.length > 0) {
|
|
116
|
+
content += `\n**Unresolved identifiers:** ${result.unresolved_identifiers.join(', ')}\n`;
|
|
117
|
+
}
|
|
118
|
+
return { content: [{ type: 'text', text: content }] };
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: `Error updating tenant servers: ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function deleteApiKey(server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
id: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: "The numeric ID of the API key to revoke.";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
required: string[];
|
|
15
|
+
};
|
|
16
|
+
handler: (args: unknown) => Promise<{
|
|
17
|
+
content: {
|
|
18
|
+
type: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
isError: boolean;
|
|
22
|
+
} | {
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=delete-api-key.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { requestConfirmation, createConfirmationSchema } from '@pulsemcp/mcp-elicitation';
|
|
3
|
+
import { readCmsAdminElicitationConfig } from '../elicitation-config.js';
|
|
4
|
+
const PARAM_DESCRIPTIONS = {
|
|
5
|
+
id: 'The numeric ID of the API key to revoke.',
|
|
6
|
+
};
|
|
7
|
+
const DeleteApiKeySchema = z.object({
|
|
8
|
+
id: z.number().int().positive().describe(PARAM_DESCRIPTIONS.id),
|
|
9
|
+
});
|
|
10
|
+
const TOOL_DESCRIPTION = `Permanently revoke (delete) an API key.
|
|
11
|
+
|
|
12
|
+
**This is a destructive operation.** Revoking an API key immediately invalidates it; any clients still using the key will start receiving 401 Unauthorized. The deletion is idempotent — calling delete on a non-existent or already-deleted key returns success.
|
|
13
|
+
|
|
14
|
+
By default, the request requires explicit user approval via MCP elicitation before being sent to the admin API.
|
|
15
|
+
|
|
16
|
+
**Parameters:**
|
|
17
|
+
- \`id\` (required): Numeric ID of the API key.
|
|
18
|
+
|
|
19
|
+
**Returns:**
|
|
20
|
+
- \`success\`: Boolean indicating whether the request succeeded.
|
|
21
|
+
- \`message\`: Human-readable confirmation message.
|
|
22
|
+
|
|
23
|
+
**Use cases:**
|
|
24
|
+
- Revoke a leaked or compromised API key.
|
|
25
|
+
- Roll a tenant's keys after a permission change.
|
|
26
|
+
- Remove a deprecated key during cleanup.`;
|
|
27
|
+
export function deleteApiKey(server, clientFactory) {
|
|
28
|
+
return {
|
|
29
|
+
name: 'delete_api_key',
|
|
30
|
+
description: TOOL_DESCRIPTION,
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
id: {
|
|
35
|
+
type: 'number',
|
|
36
|
+
description: PARAM_DESCRIPTIONS.id,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['id'],
|
|
40
|
+
},
|
|
41
|
+
handler: async (args) => {
|
|
42
|
+
try {
|
|
43
|
+
const validatedArgs = DeleteApiKeySchema.parse(args);
|
|
44
|
+
const elicitConfig = readCmsAdminElicitationConfig();
|
|
45
|
+
if (elicitConfig.destructiveElicitationEnabled) {
|
|
46
|
+
const lines = [
|
|
47
|
+
'About to PERMANENTLY REVOKE an API key via the PulseMCP admin API:',
|
|
48
|
+
` API key ID: ${validatedArgs.id}`,
|
|
49
|
+
'',
|
|
50
|
+
'Any clients still using this key will start receiving 401 Unauthorized.',
|
|
51
|
+
'This action cannot be undone.',
|
|
52
|
+
];
|
|
53
|
+
const confirmation = await requestConfirmation({
|
|
54
|
+
server,
|
|
55
|
+
message: lines.join('\n') + '\n',
|
|
56
|
+
requestedSchema: createConfirmationSchema('Revoke this API key?', 'Confirm that you want to permanently revoke this API key.'),
|
|
57
|
+
meta: {
|
|
58
|
+
'com.pulsemcp/tool-name': 'delete_api_key',
|
|
59
|
+
},
|
|
60
|
+
}, elicitConfig.base);
|
|
61
|
+
if (confirmation.action !== 'accept') {
|
|
62
|
+
if (confirmation.action === 'expired') {
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: 'API key revocation confirmation expired. Please try again.',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: 'API key revocation was cancelled by the user.',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (confirmation.content &&
|
|
83
|
+
'confirm' in confirmation.content &&
|
|
84
|
+
confirmation.content.confirm === false) {
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: 'API key revocation was not confirmed.',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const client = clientFactory();
|
|
96
|
+
const result = await client.deleteApiKey(validatedArgs.id);
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: `${result.success ? 'API key revoked.' : 'API key revocation failed.'} ${result.message}`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `Error revoking API key: ${error instanceof Error ? error.message : String(error)}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { ClientFactory } from '../server.js';
|
|
3
|
+
export declare function deleteTenant(server: Server, clientFactory: ClientFactory): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
id_or_slug: {
|
|
10
|
+
oneOf: {
|
|
11
|
+
type: string;
|
|
12
|
+
}[];
|
|
13
|
+
description: "The ID (number) or slug (string) of the tenant to delete.";
|
|
14
|
+
};
|
|
15
|
+
force: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: "When true, deletes the tenant and cascades to dependent records (API keys, enrichments, etc.). When false (default), the deletion will fail with a 422 if dependents exist.";
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
required: string[];
|
|
21
|
+
};
|
|
22
|
+
handler: (args: unknown) => Promise<{
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError: boolean;
|
|
28
|
+
} | {
|
|
29
|
+
content: {
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError?: undefined;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=delete-tenant.d.ts.map
|