pulsemcp-cms-admin-mcp-server 0.9.24 → 0.9.27

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.
@@ -24,7 +24,9 @@ export async function createApiKey(apiKey, baseUrl, params) {
24
24
  throw new Error('Invalid API key');
25
25
  }
26
26
  if (response.status === 403) {
27
- throw new Error('User lacks write privileges');
27
+ const errorData = (await response.json().catch(() => ({})));
28
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
29
+ throw new Error(message);
28
30
  }
29
31
  if (response.status === 404) {
30
32
  const errorData = (await response.json());
@@ -13,7 +13,9 @@ export async function deleteApiKey(apiKey, baseUrl, id) {
13
13
  throw new Error('Invalid API key');
14
14
  }
15
15
  if (response.status === 403) {
16
- throw new Error('User lacks write privileges');
16
+ const errorData = (await response.json().catch(() => ({})));
17
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
18
+ throw new Error(message);
17
19
  }
18
20
  if (response.status === 422) {
19
21
  const errorData = (await response.json().catch(() => ({})));
@@ -16,7 +16,9 @@ export async function deleteTenant(apiKey, baseUrl, params) {
16
16
  throw new Error('Invalid API key');
17
17
  }
18
18
  if (response.status === 403) {
19
- throw new Error('User lacks write privileges');
19
+ const errorData = (await response.json().catch(() => ({})));
20
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
21
+ throw new Error(message);
20
22
  }
21
23
  if (response.status === 404) {
22
24
  throw new Error(`Tenant ${params.id_or_slug} not found`);
@@ -0,0 +1,42 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ export async function setKnownMissingInitToolsList(apiKey, baseUrl, id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
3
+ const url = new URL(`/api/mcp_servers/${id}/known_missing_init_tools_list`, baseUrl);
4
+ const body = {
5
+ known_missing_init_tools_list: knownMissingInitToolsList,
6
+ };
7
+ if (knownMissingInitToolsListFilterTo !== undefined) {
8
+ // null is sent as JSON null; the Rails controller treats nil/blank as "clear".
9
+ body.known_missing_init_tools_list_filter_to = knownMissingInitToolsListFilterTo;
10
+ }
11
+ const response = await adminFetch(url.toString(), {
12
+ method: 'POST',
13
+ headers: {
14
+ 'X-API-Key': apiKey,
15
+ Accept: 'application/json',
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ body: JSON.stringify(body),
19
+ });
20
+ if (!response.ok) {
21
+ if (response.status === 401) {
22
+ throw new Error('Invalid API key');
23
+ }
24
+ if (response.status === 403) {
25
+ throw new Error('User lacks write privileges');
26
+ }
27
+ if (response.status === 404) {
28
+ throw new Error(`MCP server not found: ${id}`);
29
+ }
30
+ if (response.status === 400) {
31
+ const errBody = (await response.json().catch(() => ({})));
32
+ throw new Error(errBody.error ?? 'Bad request');
33
+ }
34
+ if (response.status === 422) {
35
+ const errBody = (await response.json().catch(() => ({})));
36
+ const detailStr = errBody.details?.length ? ` (${errBody.details.join(', ')})` : '';
37
+ throw new Error(`${errBody.error ?? 'Validation failed'}${detailStr}`);
38
+ }
39
+ throw new Error(`Failed to set known_missing_init_tools_list: ${response.status} ${response.statusText}`);
40
+ }
41
+ return (await response.json());
42
+ }
@@ -1079,5 +1079,15 @@ export function createMockPulseMCPAdminClient(mockData) {
1079
1079
  message: `Cache successfully refreshed for ${slug}.`,
1080
1080
  };
1081
1081
  },
1082
+ async setKnownMissingInitToolsList(id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
1083
+ return {
1084
+ id,
1085
+ slug: `mock-server-${id}`,
1086
+ known_missing_init_tools_list: knownMissingInitToolsList,
1087
+ known_missing_init_tools_list_filter_to: knownMissingInitToolsListFilterTo === undefined
1088
+ ? null
1089
+ : (knownMissingInitToolsListFilterTo ?? null),
1090
+ };
1091
+ },
1082
1092
  };
1083
1093
  }
@@ -72,6 +72,7 @@ import { getMozMetrics } from './pulsemcp-admin-client/lib/get-moz-metrics.js';
72
72
  import { getMozBacklinks } from './pulsemcp-admin-client/lib/get-moz-backlinks.js';
73
73
  import { getMozStoredMetrics } from './pulsemcp-admin-client/lib/get-moz-stored-metrics.js';
74
74
  import { recacheMCPServer } from './pulsemcp-admin-client/lib/recache-mcp-server.js';
75
+ import { setKnownMissingInitToolsList } from './pulsemcp-admin-client/lib/set-known-missing-init-tools-list.js';
75
76
  import { createTenant } from './pulsemcp-admin-client/lib/create-tenant.js';
76
77
  import { createApiKey } from './pulsemcp-admin-client/lib/create-api-key.js';
77
78
  import { deleteTenant } from './pulsemcp-admin-client/lib/delete-tenant.js';
@@ -245,6 +246,9 @@ export class PulseMCPAdminClient {
245
246
  async recacheMCPServer(slug) {
246
247
  return recacheMCPServer(this.apiKey, this.baseUrl, slug);
247
248
  }
249
+ async setKnownMissingInitToolsList(id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
250
+ return setKnownMissingInitToolsList(this.apiKey, this.baseUrl, id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo);
251
+ }
248
252
  // Redirect REST API methods
249
253
  async getRedirects(params) {
250
254
  return getRedirects(this.apiKey, this.baseUrl, params);
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ id: 'The integer id of the MCP server (the "mirror id" used elsewhere in the proctor flow)',
4
+ known_missing_init_tools_list: 'Whether the MCP server is known to be missing the init/tools-list capability. true marks the server as known-missing (proctor will skip the init/tools-list exam); false clears the flag.',
5
+ known_missing_init_tools_list_filter_to: 'Optional. When set, scopes the known-missing flag to a specific entry (e.g., "remotes[0]" or "packages[0]"). Pass an empty string or null to clear the filter. Omit the parameter entirely to leave the existing value untouched.',
6
+ };
7
+ const SetKnownMissingInitToolsListSchema = z.object({
8
+ id: z.number().int().describe(PARAM_DESCRIPTIONS.id),
9
+ known_missing_init_tools_list: z
10
+ .boolean()
11
+ .describe(PARAM_DESCRIPTIONS.known_missing_init_tools_list),
12
+ known_missing_init_tools_list_filter_to: z
13
+ .string()
14
+ .nullable()
15
+ .optional()
16
+ .describe(PARAM_DESCRIPTIONS.known_missing_init_tools_list_filter_to),
17
+ });
18
+ export function setKnownMissingInitToolsList(_server, clientFactory) {
19
+ return {
20
+ name: 'set_known_missing_init_tools_list',
21
+ description: `Update the \`known_missing_init_tools_list\` flag on an MCP server. Optionally also updates the \`known_missing_init_tools_list_filter_to\` scoping value. Identifies the server by integer id (the "mirror id" used elsewhere in the proctor flow).
22
+
23
+ Example request:
24
+ {
25
+ "id": 27765,
26
+ "known_missing_init_tools_list": true,
27
+ "known_missing_init_tools_list_filter_to": "remotes[0]"
28
+ }
29
+
30
+ Use cases:
31
+ - Mark a server as known-missing the init/tools-list capability so proctor skips that exam
32
+ - Clear the known-missing flag once the server starts responding correctly
33
+ - Scope the known-missing flag to a specific remote/package entry via the filter_to value`,
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ id: { type: 'integer', description: PARAM_DESCRIPTIONS.id },
38
+ known_missing_init_tools_list: {
39
+ type: 'boolean',
40
+ description: PARAM_DESCRIPTIONS.known_missing_init_tools_list,
41
+ },
42
+ known_missing_init_tools_list_filter_to: {
43
+ type: ['string', 'null'],
44
+ description: PARAM_DESCRIPTIONS.known_missing_init_tools_list_filter_to,
45
+ },
46
+ },
47
+ required: ['id', 'known_missing_init_tools_list'],
48
+ },
49
+ handler: async (args) => {
50
+ const validatedArgs = SetKnownMissingInitToolsListSchema.parse(args);
51
+ const client = clientFactory();
52
+ try {
53
+ const result = await client.setKnownMissingInitToolsList(validatedArgs.id, validatedArgs.known_missing_init_tools_list, validatedArgs.known_missing_init_tools_list_filter_to);
54
+ const filterDisplay = result.known_missing_init_tools_list_filter_to === null
55
+ ? '(none)'
56
+ : result.known_missing_init_tools_list_filter_to;
57
+ const text = `Updated MCP server ${result.slug} (id: ${result.id}):
58
+ - known_missing_init_tools_list: ${result.known_missing_init_tools_list}
59
+ - known_missing_init_tools_list_filter_to: ${filterDisplay}`;
60
+ return { content: [{ type: 'text', text }] };
61
+ }
62
+ catch (error) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: 'text',
67
+ text: `Error setting known_missing_init_tools_list: ${error instanceof Error ? error.message : String(error)}`,
68
+ },
69
+ ],
70
+ isError: true,
71
+ };
72
+ }
73
+ },
74
+ };
75
+ }
@@ -70,6 +70,7 @@ import { runExamForMirror } from './tools/run-exam-for-mirror.js';
70
70
  import { getExamResult } from './tools/get-exam-result.js';
71
71
  import { saveResultsForMirror } from './tools/save-results-for-mirror.js';
72
72
  import { listProctorRuns } from './tools/list-proctor-runs.js';
73
+ import { setKnownMissingInitToolsList } from './tools/set-known-missing-init-tools-list.js';
73
74
  import { getProctorMetadata } from './tools/get-proctor-metadata.js';
74
75
  // Discovered URLs tools
75
76
  import { listDiscoveredUrls } from './tools/list-discovered-urls.js';
@@ -184,13 +185,14 @@ const ALL_TOOLS = [
184
185
  { factory: getTenant, groups: ['tenants'], isWriteOperation: false },
185
186
  { factory: createTenant, groups: ['tenants'], isWriteOperation: true },
186
187
  { 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
188
+ // Destructive tenant tools opt-in only, NOT in BASE_TOOL_GROUPS.
189
+ // revoke_api_key lives here (not in `tenants`) because it calls the same
190
+ // DELETE /api/api_keys/:id endpoint as delete_api_key and the Rails admin
191
+ // API gates that endpoint behind permission_level=all (full_access). The
192
+ // standard read-write admin credential cannot DELETE, so exposing
193
+ // revoke_api_key in the regular tenants group surfaced confusing 403s on
194
+ // workflows like sub-registry credential re-issuance.
195
+ { factory: revokeApiKey, groups: ['tenants_destructive'], isWriteOperation: true },
194
196
  { factory: deleteTenant, groups: ['tenants_destructive'], isWriteOperation: true },
195
197
  { factory: deleteApiKey, groups: ['tenants_destructive'], isWriteOperation: true },
196
198
  // Tenant -> recommended MCP server association tools
@@ -267,6 +269,13 @@ const ALL_TOOLS = [
267
269
  { factory: saveResultsForMirror, groups: ['proctor'], isWriteOperation: true },
268
270
  { factory: listProctorRuns, groups: ['proctor'], isWriteOperation: false },
269
271
  { factory: getProctorMetadata, groups: ['proctor'], isWriteOperation: false },
272
+ // setKnownMissingInitToolsList flips a flag on `mcp_server` records, so it lives in
273
+ // the mcp_servers / server_directory groups (alongside recacheMCPServer), not proctor.
274
+ {
275
+ factory: setKnownMissingInitToolsList,
276
+ groups: ['mcp_servers', 'server_directory'],
277
+ isWriteOperation: true,
278
+ },
270
279
  // Discovered URLs tools
271
280
  { factory: listDiscoveredUrls, groups: ['discovered_urls'], isWriteOperation: false },
272
281
  {
@@ -402,9 +411,9 @@ function shouldIncludeTool(toolDef, enabledGroups) {
402
411
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
403
412
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
404
413
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
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.
414
+ * - tenants: Tenant management tools including API key provisioning (read + write).
406
415
  * - 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.
416
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key, revoke_api_key). NOT enabled by default; operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
408
417
  * - mcp_jsons: MCP JSON configuration tools (read + write)
409
418
  * - mcp_jsons_readonly: MCP JSON tools (read only)
410
419
  * - mcp_servers: Unified MCP server tools with abstracted interface (read + write)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsemcp-cms-admin-mcp-server",
3
- "version": "0.9.24",
3
+ "version": "0.9.27",
4
4
  "description": "Local implementation of PulseMCP CMS Admin MCP server",
5
5
  "mcpName": "com.pulsemcp.servers/pulsemcp-cms-admin",
6
6
  "main": "build/index.js",
@@ -8,13 +8,13 @@ import { type ElicitationConfig } from '@pulsemcp/mcp-elicitation';
8
8
  * 3. Per-action override: PULSEMCP_CMS_ADMIN_ELICITATION_DESTRUCTIVE
9
9
  *
10
10
  * Destructive elicitation gates the `tenants_destructive` tool group only
11
- * (delete_tenant, delete_api_key). All other write tools in the server are
12
- * not gated by elicitation.
11
+ * (delete_tenant, delete_api_key, revoke_api_key). All other write tools in
12
+ * the server are not gated by elicitation.
13
13
  */
14
14
  export interface CmsAdminElicitationConfig {
15
15
  /** Base elicitation config from the shared library */
16
16
  base: ElicitationConfig;
17
- /** Whether to elicit confirmation for destructive operations (delete_tenant, delete_api_key) */
17
+ /** Whether to elicit confirmation for destructive operations (delete_tenant, delete_api_key, revoke_api_key) */
18
18
  destructiveElicitationEnabled: boolean;
19
19
  }
20
20
  /**
@@ -24,7 +24,9 @@ export async function createApiKey(apiKey, baseUrl, params) {
24
24
  throw new Error('Invalid API key');
25
25
  }
26
26
  if (response.status === 403) {
27
- throw new Error('User lacks write privileges');
27
+ const errorData = (await response.json().catch(() => ({})));
28
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
29
+ throw new Error(message);
28
30
  }
29
31
  if (response.status === 404) {
30
32
  const errorData = (await response.json());
@@ -13,7 +13,9 @@ export async function deleteApiKey(apiKey, baseUrl, id) {
13
13
  throw new Error('Invalid API key');
14
14
  }
15
15
  if (response.status === 403) {
16
- throw new Error('User lacks write privileges');
16
+ const errorData = (await response.json().catch(() => ({})));
17
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
18
+ throw new Error(message);
17
19
  }
18
20
  if (response.status === 422) {
19
21
  const errorData = (await response.json().catch(() => ({})));
@@ -16,7 +16,9 @@ export async function deleteTenant(apiKey, baseUrl, params) {
16
16
  throw new Error('Invalid API key');
17
17
  }
18
18
  if (response.status === 403) {
19
- throw new Error('User lacks write privileges');
19
+ const errorData = (await response.json().catch(() => ({})));
20
+ const message = errorData.errors?.join(', ') || errorData.error || `Forbidden: ${response.status}`;
21
+ throw new Error(message);
20
22
  }
21
23
  if (response.status === 404) {
22
24
  throw new Error(`Tenant ${params.id_or_slug} not found`);
@@ -0,0 +1,3 @@
1
+ import type { SetKnownMissingInitToolsListResponse } from '../../types.js';
2
+ export declare function setKnownMissingInitToolsList(apiKey: string, baseUrl: string, id: number, knownMissingInitToolsList: boolean, knownMissingInitToolsListFilterTo?: string | null): Promise<SetKnownMissingInitToolsListResponse>;
3
+ //# sourceMappingURL=set-known-missing-init-tools-list.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ export async function setKnownMissingInitToolsList(apiKey, baseUrl, id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
3
+ const url = new URL(`/api/mcp_servers/${id}/known_missing_init_tools_list`, baseUrl);
4
+ const body = {
5
+ known_missing_init_tools_list: knownMissingInitToolsList,
6
+ };
7
+ if (knownMissingInitToolsListFilterTo !== undefined) {
8
+ // null is sent as JSON null; the Rails controller treats nil/blank as "clear".
9
+ body.known_missing_init_tools_list_filter_to = knownMissingInitToolsListFilterTo;
10
+ }
11
+ const response = await adminFetch(url.toString(), {
12
+ method: 'POST',
13
+ headers: {
14
+ 'X-API-Key': apiKey,
15
+ Accept: 'application/json',
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ body: JSON.stringify(body),
19
+ });
20
+ if (!response.ok) {
21
+ if (response.status === 401) {
22
+ throw new Error('Invalid API key');
23
+ }
24
+ if (response.status === 403) {
25
+ throw new Error('User lacks write privileges');
26
+ }
27
+ if (response.status === 404) {
28
+ throw new Error(`MCP server not found: ${id}`);
29
+ }
30
+ if (response.status === 400) {
31
+ const errBody = (await response.json().catch(() => ({})));
32
+ throw new Error(errBody.error ?? 'Bad request');
33
+ }
34
+ if (response.status === 422) {
35
+ const errBody = (await response.json().catch(() => ({})));
36
+ const detailStr = errBody.details?.length ? ` (${errBody.details.join(', ')})` : '';
37
+ throw new Error(`${errBody.error ?? 'Validation failed'}${detailStr}`);
38
+ }
39
+ throw new Error(`Failed to set known_missing_init_tools_list: ${response.status} ${response.statusText}`);
40
+ }
41
+ return (await response.json());
42
+ }
@@ -1079,5 +1079,15 @@ export function createMockPulseMCPAdminClient(mockData) {
1079
1079
  message: `Cache successfully refreshed for ${slug}.`,
1080
1080
  };
1081
1081
  },
1082
+ async setKnownMissingInitToolsList(id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
1083
+ return {
1084
+ id,
1085
+ slug: `mock-server-${id}`,
1086
+ known_missing_init_tools_list: knownMissingInitToolsList,
1087
+ known_missing_init_tools_list_filter_to: knownMissingInitToolsListFilterTo === undefined
1088
+ ? null
1089
+ : (knownMissingInitToolsListFilterTo ?? null),
1090
+ };
1091
+ },
1082
1092
  };
1083
1093
  }
@@ -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, 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';
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, SetKnownMissingInitToolsListResponse } from './types.js';
3
3
  export interface IPulseMCPAdminClient {
4
4
  getPosts(params?: {
5
5
  search?: string;
@@ -135,6 +135,7 @@ export interface IPulseMCPAdminClient {
135
135
  getUnifiedMCPServer(slug: string): Promise<UnifiedMCPServer>;
136
136
  updateUnifiedMCPServer(implementationId: number, params: UpdateUnifiedMCPServerParams): Promise<UnifiedMCPServer>;
137
137
  recacheMCPServer(slug: string): Promise<RecacheMCPServerResponse>;
138
+ setKnownMissingInitToolsList(id: number, knownMissingInitToolsList: boolean, knownMissingInitToolsListFilterTo?: string | null): Promise<SetKnownMissingInitToolsListResponse>;
138
139
  getRedirects(params?: {
139
140
  q?: string;
140
141
  status?: RedirectStatus;
@@ -334,6 +335,7 @@ export declare class PulseMCPAdminClient implements IPulseMCPAdminClient {
334
335
  getUnifiedMCPServer(slug: string): Promise<UnifiedMCPServer>;
335
336
  updateUnifiedMCPServer(implementationId: number, params: UpdateUnifiedMCPServerParams): Promise<UnifiedMCPServer>;
336
337
  recacheMCPServer(slug: string): Promise<RecacheMCPServerResponse>;
338
+ setKnownMissingInitToolsList(id: number, knownMissingInitToolsList: boolean, knownMissingInitToolsListFilterTo?: string | null): Promise<SetKnownMissingInitToolsListResponse>;
337
339
  getRedirects(params?: {
338
340
  q?: string;
339
341
  status?: RedirectStatus;
package/shared/server.js CHANGED
@@ -72,6 +72,7 @@ import { getMozMetrics } from './pulsemcp-admin-client/lib/get-moz-metrics.js';
72
72
  import { getMozBacklinks } from './pulsemcp-admin-client/lib/get-moz-backlinks.js';
73
73
  import { getMozStoredMetrics } from './pulsemcp-admin-client/lib/get-moz-stored-metrics.js';
74
74
  import { recacheMCPServer } from './pulsemcp-admin-client/lib/recache-mcp-server.js';
75
+ import { setKnownMissingInitToolsList } from './pulsemcp-admin-client/lib/set-known-missing-init-tools-list.js';
75
76
  import { createTenant } from './pulsemcp-admin-client/lib/create-tenant.js';
76
77
  import { createApiKey } from './pulsemcp-admin-client/lib/create-api-key.js';
77
78
  import { deleteTenant } from './pulsemcp-admin-client/lib/delete-tenant.js';
@@ -245,6 +246,9 @@ export class PulseMCPAdminClient {
245
246
  async recacheMCPServer(slug) {
246
247
  return recacheMCPServer(this.apiKey, this.baseUrl, slug);
247
248
  }
249
+ async setKnownMissingInitToolsList(id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo) {
250
+ return setKnownMissingInitToolsList(this.apiKey, this.baseUrl, id, knownMissingInitToolsList, knownMissingInitToolsListFilterTo);
251
+ }
248
252
  // Redirect REST API methods
249
253
  async getRedirects(params) {
250
254
  return getRedirects(this.apiKey, this.baseUrl, params);
@@ -0,0 +1,38 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import type { ClientFactory } from '../server.js';
3
+ export declare function setKnownMissingInitToolsList(_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 integer id of the MCP server (the \"mirror id\" used elsewhere in the proctor flow)";
12
+ };
13
+ known_missing_init_tools_list: {
14
+ type: string;
15
+ description: "Whether the MCP server is known to be missing the init/tools-list capability. true marks the server as known-missing (proctor will skip the init/tools-list exam); false clears the flag.";
16
+ };
17
+ known_missing_init_tools_list_filter_to: {
18
+ type: string[];
19
+ description: "Optional. When set, scopes the known-missing flag to a specific entry (e.g., \"remotes[0]\" or \"packages[0]\"). Pass an empty string or null to clear the filter. Omit the parameter entirely to leave the existing value untouched.";
20
+ };
21
+ };
22
+ required: string[];
23
+ };
24
+ handler: (args: unknown) => Promise<{
25
+ content: {
26
+ type: string;
27
+ text: string;
28
+ }[];
29
+ isError?: undefined;
30
+ } | {
31
+ content: {
32
+ type: string;
33
+ text: string;
34
+ }[];
35
+ isError: boolean;
36
+ }>;
37
+ };
38
+ //# sourceMappingURL=set-known-missing-init-tools-list.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ id: 'The integer id of the MCP server (the "mirror id" used elsewhere in the proctor flow)',
4
+ known_missing_init_tools_list: 'Whether the MCP server is known to be missing the init/tools-list capability. true marks the server as known-missing (proctor will skip the init/tools-list exam); false clears the flag.',
5
+ known_missing_init_tools_list_filter_to: 'Optional. When set, scopes the known-missing flag to a specific entry (e.g., "remotes[0]" or "packages[0]"). Pass an empty string or null to clear the filter. Omit the parameter entirely to leave the existing value untouched.',
6
+ };
7
+ const SetKnownMissingInitToolsListSchema = z.object({
8
+ id: z.number().int().describe(PARAM_DESCRIPTIONS.id),
9
+ known_missing_init_tools_list: z
10
+ .boolean()
11
+ .describe(PARAM_DESCRIPTIONS.known_missing_init_tools_list),
12
+ known_missing_init_tools_list_filter_to: z
13
+ .string()
14
+ .nullable()
15
+ .optional()
16
+ .describe(PARAM_DESCRIPTIONS.known_missing_init_tools_list_filter_to),
17
+ });
18
+ export function setKnownMissingInitToolsList(_server, clientFactory) {
19
+ return {
20
+ name: 'set_known_missing_init_tools_list',
21
+ description: `Update the \`known_missing_init_tools_list\` flag on an MCP server. Optionally also updates the \`known_missing_init_tools_list_filter_to\` scoping value. Identifies the server by integer id (the "mirror id" used elsewhere in the proctor flow).
22
+
23
+ Example request:
24
+ {
25
+ "id": 27765,
26
+ "known_missing_init_tools_list": true,
27
+ "known_missing_init_tools_list_filter_to": "remotes[0]"
28
+ }
29
+
30
+ Use cases:
31
+ - Mark a server as known-missing the init/tools-list capability so proctor skips that exam
32
+ - Clear the known-missing flag once the server starts responding correctly
33
+ - Scope the known-missing flag to a specific remote/package entry via the filter_to value`,
34
+ inputSchema: {
35
+ type: 'object',
36
+ properties: {
37
+ id: { type: 'integer', description: PARAM_DESCRIPTIONS.id },
38
+ known_missing_init_tools_list: {
39
+ type: 'boolean',
40
+ description: PARAM_DESCRIPTIONS.known_missing_init_tools_list,
41
+ },
42
+ known_missing_init_tools_list_filter_to: {
43
+ type: ['string', 'null'],
44
+ description: PARAM_DESCRIPTIONS.known_missing_init_tools_list_filter_to,
45
+ },
46
+ },
47
+ required: ['id', 'known_missing_init_tools_list'],
48
+ },
49
+ handler: async (args) => {
50
+ const validatedArgs = SetKnownMissingInitToolsListSchema.parse(args);
51
+ const client = clientFactory();
52
+ try {
53
+ const result = await client.setKnownMissingInitToolsList(validatedArgs.id, validatedArgs.known_missing_init_tools_list, validatedArgs.known_missing_init_tools_list_filter_to);
54
+ const filterDisplay = result.known_missing_init_tools_list_filter_to === null
55
+ ? '(none)'
56
+ : result.known_missing_init_tools_list_filter_to;
57
+ const text = `Updated MCP server ${result.slug} (id: ${result.id}):
58
+ - known_missing_init_tools_list: ${result.known_missing_init_tools_list}
59
+ - known_missing_init_tools_list_filter_to: ${filterDisplay}`;
60
+ return { content: [{ type: 'text', text }] };
61
+ }
62
+ catch (error) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: 'text',
67
+ text: `Error setting known_missing_init_tools_list: ${error instanceof Error ? error.message : String(error)}`,
68
+ },
69
+ ],
70
+ isError: true,
71
+ };
72
+ }
73
+ },
74
+ };
75
+ }
package/shared/tools.d.ts CHANGED
@@ -18,8 +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, 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.
21
+ * - tenants / tenants_readonly: Tenant management tools (CRUD + API key provisioning)
22
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key, revoke_api_key). NOT enabled by default — operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution. revoke_api_key lives here (not in `tenants`) because it calls the same DELETE /api/api_keys/:id endpoint as delete_api_key, which the Rails admin API gates behind permission_level=all.
23
23
  * - mcp_jsons / mcp_jsons_readonly: MCP JSON configuration tools
24
24
  * - mcp_servers / mcp_servers_readonly: Unified MCP server tools (abstracted interface)
25
25
  * - redirects / redirects_readonly: URL redirect management tools
@@ -61,9 +61,9 @@ export declare function parseEnabledToolGroups(enabledGroupsParam?: string): Too
61
61
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
62
62
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
63
63
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
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
+ * - tenants: Tenant management tools including API key provisioning (read + write).
65
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.
66
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key, revoke_api_key). NOT enabled by default; operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
67
67
  * - mcp_jsons: MCP JSON configuration tools (read + write)
68
68
  * - mcp_jsons_readonly: MCP JSON tools (read only)
69
69
  * - mcp_servers: Unified MCP server tools with abstracted interface (read + write)
package/shared/tools.js CHANGED
@@ -70,6 +70,7 @@ import { runExamForMirror } from './tools/run-exam-for-mirror.js';
70
70
  import { getExamResult } from './tools/get-exam-result.js';
71
71
  import { saveResultsForMirror } from './tools/save-results-for-mirror.js';
72
72
  import { listProctorRuns } from './tools/list-proctor-runs.js';
73
+ import { setKnownMissingInitToolsList } from './tools/set-known-missing-init-tools-list.js';
73
74
  import { getProctorMetadata } from './tools/get-proctor-metadata.js';
74
75
  // Discovered URLs tools
75
76
  import { listDiscoveredUrls } from './tools/list-discovered-urls.js';
@@ -184,13 +185,14 @@ const ALL_TOOLS = [
184
185
  { factory: getTenant, groups: ['tenants'], isWriteOperation: false },
185
186
  { factory: createTenant, groups: ['tenants'], isWriteOperation: true },
186
187
  { 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
188
+ // Destructive tenant tools opt-in only, NOT in BASE_TOOL_GROUPS.
189
+ // revoke_api_key lives here (not in `tenants`) because it calls the same
190
+ // DELETE /api/api_keys/:id endpoint as delete_api_key and the Rails admin
191
+ // API gates that endpoint behind permission_level=all (full_access). The
192
+ // standard read-write admin credential cannot DELETE, so exposing
193
+ // revoke_api_key in the regular tenants group surfaced confusing 403s on
194
+ // workflows like sub-registry credential re-issuance.
195
+ { factory: revokeApiKey, groups: ['tenants_destructive'], isWriteOperation: true },
194
196
  { factory: deleteTenant, groups: ['tenants_destructive'], isWriteOperation: true },
195
197
  { factory: deleteApiKey, groups: ['tenants_destructive'], isWriteOperation: true },
196
198
  // Tenant -> recommended MCP server association tools
@@ -267,6 +269,13 @@ const ALL_TOOLS = [
267
269
  { factory: saveResultsForMirror, groups: ['proctor'], isWriteOperation: true },
268
270
  { factory: listProctorRuns, groups: ['proctor'], isWriteOperation: false },
269
271
  { factory: getProctorMetadata, groups: ['proctor'], isWriteOperation: false },
272
+ // setKnownMissingInitToolsList flips a flag on `mcp_server` records, so it lives in
273
+ // the mcp_servers / server_directory groups (alongside recacheMCPServer), not proctor.
274
+ {
275
+ factory: setKnownMissingInitToolsList,
276
+ groups: ['mcp_servers', 'server_directory'],
277
+ isWriteOperation: true,
278
+ },
270
279
  // Discovered URLs tools
271
280
  { factory: listDiscoveredUrls, groups: ['discovered_urls'], isWriteOperation: false },
272
281
  {
@@ -402,9 +411,9 @@ function shouldIncludeTool(toolDef, enabledGroups) {
402
411
  * - unofficial_mirrors: Unofficial mirror CRUD tools (read + write)
403
412
  * - unofficial_mirrors_readonly: Unofficial mirror tools (read only)
404
413
  * - official_mirrors_readonly: Official mirrors REST API tools (read only)
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.
414
+ * - tenants: Tenant management tools including API key provisioning (read + write).
406
415
  * - 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.
416
+ * - tenants_destructive: Destructive tenant tools (delete_tenant, delete_api_key, revoke_api_key). NOT enabled by default; operators must opt in via TOOL_GROUPS. Each tool requires MCP elicitation user approval before execution.
408
417
  * - mcp_jsons: MCP JSON configuration tools (read + write)
409
418
  * - mcp_jsons_readonly: MCP JSON tools (read only)
410
419
  * - mcp_servers: Unified MCP server tools with abstracted interface (read + write)
package/shared/types.d.ts CHANGED
@@ -891,6 +891,12 @@ export interface DeleteApiKeyResponse {
891
891
  export interface RecacheMCPServerResponse {
892
892
  message: string;
893
893
  }
894
+ export interface SetKnownMissingInitToolsListResponse {
895
+ id: number;
896
+ slug: string;
897
+ known_missing_init_tools_list: boolean;
898
+ known_missing_init_tools_list_filter_to: string | null;
899
+ }
894
900
  export interface MozMetrics {
895
901
  page_authority?: number;
896
902
  domain_authority?: number;