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.
Files changed (75) hide show
  1. package/README.md +93 -74
  2. package/build/index.js +53 -1
  3. package/build/shared/src/elicitation-config.js +69 -0
  4. package/build/shared/src/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
  5. package/build/shared/src/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
  6. package/build/shared/src/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
  7. package/build/shared/src/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
  8. package/build/shared/src/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
  9. package/build/shared/src/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
  10. package/build/shared/src/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
  11. package/build/shared/src/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
  12. package/build/shared/src/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
  13. package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +29 -0
  14. package/build/shared/src/server.js +16 -0
  15. package/build/shared/src/tools/add-servers-to-tenant.js +133 -0
  16. package/build/shared/src/tools/delete-api-key.js +119 -0
  17. package/build/shared/src/tools/delete-tenant.js +129 -0
  18. package/build/shared/src/tools/get-mcp-server.js +26 -8
  19. package/build/shared/src/tools/list-mcp-servers.js +3 -0
  20. package/build/shared/src/tools/list-tenant-servers.js +89 -0
  21. package/build/shared/src/tools/remove-servers-from-tenant.js +92 -0
  22. package/build/shared/src/tools/revoke-api-key.js +119 -0
  23. package/build/shared/src/tools/save-mcp-implementation.js +89 -2
  24. package/build/shared/src/tools/update-mcp-server.js +19 -0
  25. package/build/shared/src/tools.js +22 -1
  26. package/node_modules/@pulsemcp/mcp-elicitation/build/config.d.ts +15 -0
  27. package/node_modules/@pulsemcp/mcp-elicitation/build/config.js +41 -0
  28. package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.d.ts +24 -0
  29. package/node_modules/@pulsemcp/mcp-elicitation/build/elicitation.js +175 -0
  30. package/node_modules/@pulsemcp/mcp-elicitation/build/index.d.ts +3 -0
  31. package/node_modules/@pulsemcp/mcp-elicitation/build/index.js +2 -0
  32. package/node_modules/@pulsemcp/mcp-elicitation/build/types.d.ts +114 -0
  33. package/node_modules/@pulsemcp/mcp-elicitation/build/types.js +1 -0
  34. package/node_modules/@pulsemcp/mcp-elicitation/package.json +28 -0
  35. package/package.json +7 -1
  36. package/shared/elicitation-config.d.ts +61 -0
  37. package/shared/elicitation-config.js +69 -0
  38. package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.d.ts +3 -0
  39. package/shared/pulsemcp-admin-client/lib/bulk-update-tenant-servers.js +41 -0
  40. package/shared/pulsemcp-admin-client/lib/create-mcp-implementation.js +7 -0
  41. package/shared/pulsemcp-admin-client/lib/delete-api-key.d.ts +3 -0
  42. package/shared/pulsemcp-admin-client/lib/delete-api-key.js +25 -0
  43. package/shared/pulsemcp-admin-client/lib/delete-tenant.d.ts +3 -0
  44. package/shared/pulsemcp-admin-client/lib/delete-tenant.js +31 -0
  45. package/shared/pulsemcp-admin-client/lib/get-unified-mcp-server.js +5 -1
  46. package/shared/pulsemcp-admin-client/lib/list-tenant-servers.d.ts +7 -0
  47. package/shared/pulsemcp-admin-client/lib/list-tenant-servers.js +57 -0
  48. package/shared/pulsemcp-admin-client/lib/save-mcp-implementation.js +8 -0
  49. package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.d.ts +2 -0
  50. package/shared/pulsemcp-admin-client/lib/unified-mcp-server-mapper.js +3 -0
  51. package/shared/pulsemcp-admin-client/lib/update-unified-mcp-server.js +14 -0
  52. package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +29 -0
  53. package/shared/server.d.ts +17 -1
  54. package/shared/server.js +16 -0
  55. package/shared/tools/add-servers-to-tenant.d.ts +57 -0
  56. package/shared/tools/add-servers-to-tenant.js +133 -0
  57. package/shared/tools/delete-api-key.d.ts +30 -0
  58. package/shared/tools/delete-api-key.js +119 -0
  59. package/shared/tools/delete-tenant.d.ts +36 -0
  60. package/shared/tools/delete-tenant.js +129 -0
  61. package/shared/tools/get-mcp-server.js +26 -8
  62. package/shared/tools/list-mcp-servers.js +3 -0
  63. package/shared/tools/list-tenant-servers.d.ts +45 -0
  64. package/shared/tools/list-tenant-servers.js +89 -0
  65. package/shared/tools/remove-servers-from-tenant.d.ts +42 -0
  66. package/shared/tools/remove-servers-from-tenant.js +92 -0
  67. package/shared/tools/revoke-api-key.d.ts +30 -0
  68. package/shared/tools/revoke-api-key.js +119 -0
  69. package/shared/tools/save-mcp-implementation.d.ts +9 -1
  70. package/shared/tools/save-mcp-implementation.js +89 -2
  71. package/shared/tools/update-mcp-server.d.ts +6 -0
  72. package/shared/tools/update-mcp-server.js +19 -0
  73. package/shared/tools.d.ts +5 -3
  74. package/shared/tools.js +22 -1
  75. 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}.`,
@@ -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