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
@@ -1,4 +1,29 @@
1
1
  import { z } from 'zod';
2
+ // Hosts that must never appear as canonical URLs. Source-code hosts belong in
3
+ // `source_code_location`; aggregators are not authoritative. Mirrors the rule
4
+ // in agents/skills/server-discovery/identify-remote-canonical-url/SKILL.md.
5
+ export const BLOCKLISTED_CANONICAL_HOSTS = [
6
+ 'github.com',
7
+ 'gitlab.com',
8
+ 'bitbucket.org',
9
+ 'smithery.ai',
10
+ 'glama.ai',
11
+ ];
12
+ export function findBlocklistedCanonicalHost(url) {
13
+ let host;
14
+ try {
15
+ host = new URL(url).hostname.toLowerCase();
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ for (const blocked of BLOCKLISTED_CANONICAL_HOSTS) {
21
+ if (host === blocked || host.endsWith(`.${blocked}`)) {
22
+ return host;
23
+ }
24
+ }
25
+ return null;
26
+ }
2
27
  // Parameter descriptions - single source of truth
3
28
  const PARAM_DESCRIPTIONS = {
4
29
  id: 'The ID of the MCP implementation to update. Omit this field to CREATE a new implementation instead of updating an existing one.',
@@ -26,11 +51,12 @@ const PARAM_DESCRIPTIONS = {
26
51
  // Remote endpoints
27
52
  remote: 'Array of remote endpoint configurations for MCP servers. Providing this replaces ALL existing remotes. Omitting leaves them unchanged. Pass an empty array to delete all. Each remote can have: id (existing remote ID or blank for new), url_direct, url_setup, transport (e.g., "sse"), host_platform (e.g., "smithery"), host_infrastructure (e.g., "cloudflare"), authentication_method (e.g., "open"), cost (e.g., "free"), status (defaults to "live"), display_name, and internal_notes.',
28
53
  // Canonical URLs
29
- canonical: 'Array of canonical URL configurations. Providing this replaces ALL existing canonical URLs. Omitting leaves them unchanged. Pass an empty array to delete all. Each entry must have: url (the canonical URL), scope (one of "domain", "subdomain", or "url"), and optional note for additional context.',
54
+ canonical: 'Array of canonical URL configurations. Providing this replaces ALL existing canonical URLs. Omitting leaves them unchanged. Pass an empty array to delete all. Each entry must have: url (the canonical URL), scope (one of "domain", "subdomain", or "url"), and optional note for additional context. The host must NOT be one of: github.com, gitlab.com, bitbucket.org, smithery.ai, glama.ai (or any subdomain of those) — source-code repos belong in `source_code_location`, and aggregator listings are not authoritative. Calls with blocklisted canonical hosts are rejected with `CANONICAL_BLOCKLISTED_HOST`.',
30
55
  // Flags
31
56
  verified_no_remote_canonicals: 'Mark that this server has been verified to have no remote canonical URLs (true = verified no remote canonicals exist, false = reset/canonicals found)',
32
57
  // Other fields
33
58
  internal_notes: 'Admin-only notes. Not displayed publicly. Used for tracking submission sources, reviewer comments, etc.',
59
+ owner_tenant: 'Owner tenant of this server. Pass a tenant slug (e.g. "gram-recommended") or numeric tenant ID to link, or null to clear the link. Omit to leave unchanged. Slugs are preferred for readability; the Rails backend resolves them to a tenant ID. An unknown slug returns a validation error.',
34
60
  };
35
61
  const SaveMCPImplementationSchema = z.object({
36
62
  id: z.number().optional().describe(PARAM_DESCRIPTIONS.id),
@@ -97,6 +123,10 @@ const SaveMCPImplementationSchema = z.object({
97
123
  .describe(PARAM_DESCRIPTIONS.verified_no_remote_canonicals),
98
124
  // Other fields
99
125
  internal_notes: z.string().optional().describe(PARAM_DESCRIPTIONS.internal_notes),
126
+ owner_tenant: z
127
+ .union([z.string(), z.number(), z.null()])
128
+ .optional()
129
+ .describe(PARAM_DESCRIPTIONS.owner_tenant),
100
130
  });
101
131
  export function saveMCPImplementation(_server, clientFactory) {
102
132
  return {
@@ -314,13 +344,48 @@ Important notes:
314
344
  type: 'string',
315
345
  description: PARAM_DESCRIPTIONS.internal_notes,
316
346
  },
347
+ // Owner tenant
348
+ owner_tenant: {
349
+ oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'null' }],
350
+ description: PARAM_DESCRIPTIONS.owner_tenant,
351
+ },
317
352
  },
318
353
  },
319
354
  handler: async (args) => {
320
355
  const validatedArgs = SaveMCPImplementationSchema.parse(args);
356
+ if (validatedArgs.canonical) {
357
+ for (const entry of validatedArgs.canonical) {
358
+ const blockedHost = findBlocklistedCanonicalHost(entry.url);
359
+ if (blockedHost) {
360
+ return {
361
+ content: [
362
+ {
363
+ type: 'text',
364
+ text: `CANONICAL_BLOCKLISTED_HOST: ${blockedHost} is not a valid canonical URL host. Use the actual project homepage instead.`,
365
+ },
366
+ ],
367
+ isError: true,
368
+ };
369
+ }
370
+ }
371
+ }
321
372
  const client = clientFactory();
322
373
  try {
323
- const { id, type, ...restParams } = validatedArgs;
374
+ const { id, type, owner_tenant, ...restParams } = validatedArgs;
375
+ // Translate owner_tenant (string slug | number id | null clear) into the
376
+ // explicit owner_tenant_slug / owner_tenant_id fields the admin client
377
+ // forwards to Rails.
378
+ if (owner_tenant !== undefined) {
379
+ if (typeof owner_tenant === 'string') {
380
+ restParams.owner_tenant_slug = owner_tenant;
381
+ }
382
+ else if (typeof owner_tenant === 'number') {
383
+ restParams.owner_tenant_id = owner_tenant;
384
+ }
385
+ else {
386
+ restParams.owner_tenant_id = null;
387
+ }
388
+ }
324
389
  // Determine if this is a create or update operation
325
390
  const isCreate = id === undefined;
326
391
  if (isCreate) {
@@ -386,6 +451,15 @@ Important notes:
386
451
  content += ` - ${r.display_name || r.url_direct || `ID ${r.id}`}\n`;
387
452
  });
388
453
  }
454
+ const createOwnerSlug = implementation.mcp_server?.owner_tenant_slug;
455
+ const createOwnerId = implementation.mcp_server?.owner_tenant_id;
456
+ if (createOwnerSlug || createOwnerId) {
457
+ content += `**Owner Tenant:** ${createOwnerSlug ?? '(no slug)'}`;
458
+ if (createOwnerId) {
459
+ content += ` (id: ${createOwnerId})`;
460
+ }
461
+ content += '\n';
462
+ }
389
463
  if (implementation.created_at) {
390
464
  content += `**Created:** ${new Date(implementation.created_at).toLocaleDateString()}\n`;
391
465
  }
@@ -459,6 +533,19 @@ Important notes:
459
533
  content += ` - ${r.display_name || r.url_direct || `ID ${r.id}`}\n`;
460
534
  });
461
535
  }
536
+ const updateOwnerSlug = implementation.mcp_server?.owner_tenant_slug;
537
+ const updateOwnerId = implementation.mcp_server?.owner_tenant_id;
538
+ if (updateOwnerSlug || updateOwnerId) {
539
+ content += `**Owner Tenant:** ${updateOwnerSlug ?? '(no slug)'}`;
540
+ if (updateOwnerId) {
541
+ content += ` (id: ${updateOwnerId})`;
542
+ }
543
+ content += '\n';
544
+ }
545
+ else if (implementation.mcp_server?.owner_tenant_id === null &&
546
+ implementation.mcp_server?.owner_tenant_slug === null) {
547
+ content += `**Owner Tenant:** (none)\n`;
548
+ }
462
549
  if (implementation.updated_at) {
463
550
  content += `**Updated:** ${new Date(implementation.updated_at).toLocaleDateString()}\n`;
464
551
  }
@@ -183,6 +183,12 @@ export declare function updateMCPServer(_server: Server, clientFactory: ClientFa
183
183
  type: string;
184
184
  description: "Admin-only internal notes";
185
185
  };
186
+ owner_tenant: {
187
+ oneOf: {
188
+ type: string;
189
+ }[];
190
+ description: "Owner tenant of this server. Pass a tenant slug (e.g. \"gram-recommended\") or numeric tenant ID to link, or null to clear the link. Omit to leave unchanged. Slugs are preferred for readability; the Rails backend resolves them to a tenant ID. An unknown slug returns a validation error.";
191
+ };
186
192
  };
187
193
  required: string[];
188
194
  };
@@ -22,6 +22,7 @@ const PARAM_DESCRIPTIONS = {
22
22
  canonical_urls: 'Authoritative URLs for the server. Providing this replaces ALL existing canonical URLs. Omitting leaves them unchanged. Pass an empty array to delete all.',
23
23
  remotes: 'Remote endpoints for the server. Providing this replaces ALL existing remotes. Omitting leaves them unchanged. Pass an empty array to delete all.',
24
24
  internal_notes: 'Admin-only internal notes',
25
+ owner_tenant: 'Owner tenant of this server. Pass a tenant slug (e.g. "gram-recommended") or numeric tenant ID to link, or null to clear the link. Omit to leave unchanged. Slugs are preferred for readability; the Rails backend resolves them to a tenant ID. An unknown slug returns a validation error.',
25
26
  };
26
27
  const CanonicalUrlSchema = z.object({
27
28
  url: z.string().describe('The canonical URL'),
@@ -95,6 +96,10 @@ const UpdateMCPServerSchema = z.object({
95
96
  .describe(PARAM_DESCRIPTIONS.canonical_urls),
96
97
  remotes: z.array(RemoteEndpointSchema).optional().describe(PARAM_DESCRIPTIONS.remotes),
97
98
  internal_notes: z.string().optional().describe(PARAM_DESCRIPTIONS.internal_notes),
99
+ owner_tenant: z
100
+ .union([z.string(), z.number(), z.null()])
101
+ .optional()
102
+ .describe(PARAM_DESCRIPTIONS.owner_tenant),
98
103
  });
99
104
  export function updateMCPServer(_server, clientFactory) {
100
105
  return {
@@ -284,6 +289,10 @@ Create new provider:
284
289
  description: PARAM_DESCRIPTIONS.remotes,
285
290
  },
286
291
  internal_notes: { type: 'string', description: PARAM_DESCRIPTIONS.internal_notes },
292
+ owner_tenant: {
293
+ oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'null' }],
294
+ description: PARAM_DESCRIPTIONS.owner_tenant,
295
+ },
287
296
  },
288
297
  required: ['implementation_id'],
289
298
  },
@@ -335,6 +344,16 @@ Create new provider:
335
344
  if (server.verified_no_remote_canonicals !== undefined) {
336
345
  content += `**Verified No Remote Canonicals:** ${server.verified_no_remote_canonicals ? 'Yes' : 'No'}\n`;
337
346
  }
347
+ if (server.owner_tenant_slug || server.owner_tenant_id) {
348
+ content += `**Owner Tenant:** ${server.owner_tenant_slug ?? '(no slug)'}`;
349
+ if (server.owner_tenant_id) {
350
+ content += ` (id: ${server.owner_tenant_id})`;
351
+ }
352
+ content += '\n';
353
+ }
354
+ else if (server.owner_tenant_id === null && server.owner_tenant_slug === null) {
355
+ content += `**Owner Tenant:** (none)\n`;
356
+ }
338
357
  if (server.updated_at) {
339
358
  content += `**Updated:** ${server.updated_at}\n`;
340
359
  }
package/shared/tools.d.ts CHANGED
@@ -18,7 +18,8 @@ import { ClientFactory } from './server.js';
18
18
  * - official_queue / official_queue_readonly: Official mirror queue tools (list, get, approve, reject, unlink)
19
19
  * - unofficial_mirrors / unofficial_mirrors_readonly: Unofficial mirror CRUD tools
20
20
  * - official_mirrors_readonly: Official mirrors read-only tools (REST API)
21
- * - tenants / tenants_readonly: Tenant management tools (CRUD + API key provisioning)
21
+ * - tenants / tenants_readonly: Tenant management tools (CRUD + API key provisioning, including revoke_api_key for rolling keys after re-issuance)
22
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key). NOT enabled by default — operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
22
23
  * - mcp_jsons / mcp_jsons_readonly: MCP JSON configuration tools
23
24
  * - mcp_servers / mcp_servers_readonly: Unified MCP server tools (abstracted interface)
24
25
  * - redirects / redirects_readonly: URL redirect management tools
@@ -28,7 +29,7 @@ import { ClientFactory } from './server.js';
28
29
  * - moz / moz_readonly: MOZ SEO metrics tools (live URL metrics, backlinks, stored historical data)
29
30
  * - notifications: Notification email tools (send_impl_posted_notif). Separated from server_directory so notification capability can be granted independently.
30
31
  */
31
- export type ToolGroup = 'newsletter' | 'newsletter_readonly' | 'server_directory' | 'server_directory_readonly' | 'official_queue' | 'official_queue_readonly' | 'unofficial_mirrors' | 'unofficial_mirrors_readonly' | 'official_mirrors' | 'official_mirrors_readonly' | 'tenants' | 'tenants_readonly' | 'mcp_jsons' | 'mcp_jsons_readonly' | 'mcp_servers' | 'mcp_servers_readonly' | 'redirects' | 'redirects_readonly' | 'good_jobs' | 'good_jobs_readonly' | 'proctor' | 'proctor_readonly' | 'discovered_urls' | 'discovered_urls_readonly' | 'moz' | 'moz_readonly' | 'notifications';
32
+ export type ToolGroup = 'newsletter' | 'newsletter_readonly' | 'server_directory' | 'server_directory_readonly' | 'official_queue' | 'official_queue_readonly' | 'unofficial_mirrors' | 'unofficial_mirrors_readonly' | 'official_mirrors' | 'official_mirrors_readonly' | 'tenants' | 'tenants_readonly' | 'tenants_destructive' | 'mcp_jsons' | 'mcp_jsons_readonly' | 'mcp_servers' | 'mcp_servers_readonly' | 'redirects' | 'redirects_readonly' | 'good_jobs' | 'good_jobs_readonly' | 'proctor' | 'proctor_readonly' | 'discovered_urls' | 'discovered_urls_readonly' | 'moz' | 'moz_readonly' | 'notifications';
32
33
  /**
33
34
  * Parse enabled tool groups from environment variable or parameter
34
35
  * @param enabledGroupsParam - Comma-separated list of tool groups (e.g., "newsletter,server_directory_readonly")
@@ -60,8 +61,9 @@ export declare function parseEnabledToolGroups(enabledGroupsParam?: string): Too
60
61
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
61
62
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
62
63
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
63
- * - tenants: Tenant management tools including API key provisioning (read + write)
64
+ * - tenants: Tenant management tools including API key provisioning + revoke_api_key (read + write). revoke_api_key is gated by MCP elicitation user approval before execution.
64
65
  * - tenants_readonly: Tenant tools (read only)
66
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key). NOT enabled by default; operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
65
67
  * - mcp_jsons: MCP JSON configuration tools (read + write)
66
68
  * - mcp_jsons_readonly: MCP JSON tools (read only)
67
69
  * - mcp_servers: Unified MCP server tools with abstracted interface (read + write)
package/shared/tools.js CHANGED
@@ -31,6 +31,12 @@ import { getTenants } from './tools/get-tenants.js';
31
31
  import { getTenant } from './tools/get-tenant.js';
32
32
  import { createTenant } from './tools/create-tenant.js';
33
33
  import { createApiKey } from './tools/create-api-key.js';
34
+ import { revokeApiKey } from './tools/revoke-api-key.js';
35
+ import { deleteTenant } from './tools/delete-tenant.js';
36
+ import { deleteApiKey } from './tools/delete-api-key.js';
37
+ import { listTenantServers } from './tools/list-tenant-servers.js';
38
+ import { addServersToTenant } from './tools/add-servers-to-tenant.js';
39
+ import { removeServersFromTenant } from './tools/remove-servers-from-tenant.js';
34
40
  // MCP JSON tools
35
41
  import { getMcpJsons } from './tools/get-mcp-jsons.js';
36
42
  import { getMcpJson } from './tools/get-mcp-json.js';
@@ -178,6 +184,19 @@ const ALL_TOOLS = [
178
184
  { factory: getTenant, groups: ['tenants'], isWriteOperation: false },
179
185
  { factory: createTenant, groups: ['tenants'], isWriteOperation: true },
180
186
  { factory: createApiKey, groups: ['tenants'], isWriteOperation: true },
187
+ // revoke_api_key lives in the regular `tenants` group (not `tenants_destructive`) so
188
+ // it's exposed to read-write tenant management workflows like sub-registry credential
189
+ // re-issuance. The tool gates each call with MCP elicitation; if the connected client
190
+ // supports neither native elicitation nor an HTTP fallback, the elicitation library
191
+ // throws at runtime rather than silently destroying the key.
192
+ { factory: revokeApiKey, groups: ['tenants'], isWriteOperation: true },
193
+ // Destructive tenant tools — opt-in only, NOT in BASE_TOOL_GROUPS
194
+ { factory: deleteTenant, groups: ['tenants_destructive'], isWriteOperation: true },
195
+ { factory: deleteApiKey, groups: ['tenants_destructive'], isWriteOperation: true },
196
+ // Tenant -> recommended MCP server association tools
197
+ { factory: listTenantServers, groups: ['tenants'], isWriteOperation: false },
198
+ { factory: addServersToTenant, groups: ['tenants'], isWriteOperation: true },
199
+ { factory: removeServersFromTenant, groups: ['tenants'], isWriteOperation: true },
181
200
  // MCP JSON tools (CRUD) (also in server_directory)
182
201
  {
183
202
  factory: getMcpJsons,
@@ -277,6 +296,7 @@ const VALID_TOOL_GROUPS = [
277
296
  'official_mirrors_readonly',
278
297
  'tenants',
279
298
  'tenants_readonly',
299
+ 'tenants_destructive',
280
300
  'mcp_jsons',
281
301
  'mcp_jsons_readonly',
282
302
  'mcp_servers',
@@ -382,8 +402,9 @@ function shouldIncludeTool(toolDef, enabledGroups) {
382
402
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
383
403
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
384
404
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
385
- * - tenants: Tenant management tools including API key provisioning (read + write)
405
+ * - tenants: Tenant management tools including API key provisioning + revoke_api_key (read + write). revoke_api_key is gated by MCP elicitation user approval before execution.
386
406
  * - tenants_readonly: Tenant tools (read only)
407
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key). NOT enabled by default; operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
387
408
  * - mcp_jsons: MCP JSON configuration tools (read + write)
388
409
  * - mcp_jsons_readonly: MCP JSON tools (read only)
389
410
  * - mcp_servers: Unified MCP server tools with abstracted interface (read + write)
package/shared/types.d.ts CHANGED
@@ -126,6 +126,8 @@ export interface MCPServer {
126
126
  mcp_server_remotes_count?: number;
127
127
  recommended?: boolean;
128
128
  verified_no_remote_canonicals?: boolean;
129
+ owner_tenant_id?: number | null;
130
+ owner_tenant_slug?: string | null;
129
131
  tags?: MCPServerTag[];
130
132
  remotes?: MCPServerRemote[];
131
133
  }
@@ -196,6 +198,8 @@ export interface SaveMCPImplementationParams {
196
198
  implementation_language?: string;
197
199
  mcp_server_id?: number | null;
198
200
  mcp_client_id?: number | null;
201
+ owner_tenant_id?: number | null;
202
+ owner_tenant_slug?: string | null;
199
203
  provider_id?: string | number;
200
204
  provider_slug?: string;
201
205
  provider_url?: string;
@@ -247,6 +251,8 @@ export interface CreateMCPImplementationParams {
247
251
  remote?: RemoteEndpointParams[];
248
252
  canonical?: CanonicalUrlParams[];
249
253
  internal_notes?: string;
254
+ owner_tenant_id?: number | null;
255
+ owner_tenant_slug?: string | null;
250
256
  }
251
257
  export interface Provider {
252
258
  id: number;
@@ -427,6 +433,68 @@ export interface TenantsResponse {
427
433
  limit?: number;
428
434
  };
429
435
  }
436
+ export interface TenantServer {
437
+ id: number;
438
+ tenant_id: number;
439
+ mcp_server_id: number;
440
+ mcp_server_slug: string;
441
+ status: 'active' | 'deleted';
442
+ server_json_selection: string;
443
+ first_touched_at?: string | null;
444
+ touched: boolean;
445
+ created_at?: string;
446
+ updated_at?: string;
447
+ }
448
+ export interface ListTenantServersResponse {
449
+ data: TenantServer[];
450
+ pagination: {
451
+ current_page: number;
452
+ total_pages: number;
453
+ total_count: number;
454
+ has_next: boolean;
455
+ limit: number;
456
+ };
457
+ }
458
+ export interface BulkUpdateTenantServersParams {
459
+ add_server_identifiers?: Array<number | string>;
460
+ remove_server_identifiers?: Array<number | string>;
461
+ restore_association_ids?: number[];
462
+ }
463
+ export interface BulkUpdateAddedItem {
464
+ mcp_server_id: number;
465
+ mcp_server_slug: string;
466
+ association_id: number;
467
+ outcome: 'created' | 'restored_from_deleted';
468
+ }
469
+ export interface BulkUpdateRemovedItem {
470
+ mcp_server_id: number;
471
+ mcp_server_slug: string;
472
+ association_id: number | null;
473
+ outcome: 'hard_deleted' | 'soft_deleted';
474
+ }
475
+ export interface BulkUpdateRestoredItem {
476
+ mcp_server_id: number;
477
+ mcp_server_slug: string;
478
+ association_id: number;
479
+ }
480
+ export interface BulkUpdateSkippedItem {
481
+ mcp_server_id?: number;
482
+ mcp_server_slug?: string;
483
+ association_id?: number;
484
+ reason: 'no_unofficial_mirror' | 'already_active' | string;
485
+ }
486
+ export interface BulkUpdateTenantServersResponse {
487
+ status: 'success';
488
+ tenant: {
489
+ id: number;
490
+ slug: string;
491
+ };
492
+ added: BulkUpdateAddedItem[];
493
+ removed: BulkUpdateRemovedItem[];
494
+ restored: BulkUpdateRestoredItem[];
495
+ skipped: BulkUpdateSkippedItem[];
496
+ unresolved_identifiers: Array<number | string>;
497
+ }
430
498
  export interface McpJson {
431
499
  id: number;
432
500
  mcp_servers_unofficial_mirror_id: number;
@@ -529,6 +597,8 @@ export interface UnifiedMCPServer {
529
597
  downloads_estimate_last_30_days?: number;
530
598
  downloads_estimate_total?: number;
531
599
  internal_notes?: string;
600
+ owner_tenant_id?: number | null;
601
+ owner_tenant_slug?: string | null;
532
602
  created_at?: string;
533
603
  updated_at?: string;
534
604
  created_on_override?: string;
@@ -575,6 +645,7 @@ export interface UpdateUnifiedMCPServerParams {
575
645
  canonical_urls?: CanonicalUrl[];
576
646
  remotes?: RemoteEndpoint[];
577
647
  internal_notes?: string;
648
+ owner_tenant?: string | number | null;
578
649
  }
579
650
  export type RedirectStatus = 'draft' | 'active' | 'paused' | 'archived';
580
651
  export interface Redirect {
@@ -805,6 +876,18 @@ export interface CreateApiKeyParams {
805
876
  name?: string;
806
877
  permission_level?: ApiKeyPermissionLevel;
807
878
  }
879
+ export interface DeleteTenantParams {
880
+ id_or_slug: number | string;
881
+ force?: boolean;
882
+ }
883
+ export interface DeleteTenantResponse {
884
+ success: boolean;
885
+ message: string;
886
+ }
887
+ export interface DeleteApiKeyResponse {
888
+ success: boolean;
889
+ message: string;
890
+ }
808
891
  export interface RecacheMCPServerResponse {
809
892
  message: string;
810
893
  }