pulsemcp-cms-admin-mcp-server 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ /**
3
+ * Enable or disable the one-off SYSTEMIC_DROP bypass for popularity estimates.
4
+ *
5
+ * POST /api/popularity_drop_bypass with body { enabled } sets the flag and
6
+ * returns the resulting status. When enabled, the NEXT run of
7
+ * UpdatePopularityEstimatesFromBigqueryJob flushes legitimate drops held by the
8
+ * SYSTEMIC_DROP guardrail, then consumes (auto-resets) the flag.
9
+ */
10
+ export async function setPopularityDropBypass(apiKey, baseUrl, enabled) {
11
+ const url = new URL('/api/popularity_drop_bypass', baseUrl);
12
+ const response = await adminFetch(url.toString(), {
13
+ method: 'POST',
14
+ headers: {
15
+ 'X-API-Key': apiKey,
16
+ Accept: 'application/json',
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ body: JSON.stringify({ enabled }),
20
+ });
21
+ if (!response.ok) {
22
+ if (response.status === 401) {
23
+ throw new Error('Invalid API key');
24
+ }
25
+ if (response.status === 403) {
26
+ throw new Error('User lacks write privileges');
27
+ }
28
+ if (response.status === 404) {
29
+ throw new Error('Popularity drop bypass endpoint not found');
30
+ }
31
+ if (response.status === 422) {
32
+ const errorData = (await response.json().catch(() => ({})));
33
+ throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
34
+ }
35
+ throw new Error(`Failed to set popularity drop bypass: ${response.status} ${response.statusText}`);
36
+ }
37
+ return (await response.json());
38
+ }
@@ -940,6 +940,13 @@ export function createMockPulseMCPAdminClient(mockData) {
940
940
  deleted_count: 0,
941
941
  };
942
942
  },
943
+ async setPopularityDropBypass(enabled) {
944
+ return {
945
+ enabled,
946
+ enabled_at: enabled ? '2026-06-09T00:00:00Z' : null,
947
+ enabled_by: enabled ? 'mock-admin' : null,
948
+ };
949
+ },
943
950
  // Proctor methods
944
951
  async runExamForMirror() {
945
952
  return {
@@ -60,6 +60,7 @@ import { retryGoodJob } from './pulsemcp-admin-client/lib/retry-good-job.js';
60
60
  import { discardGoodJob } from './pulsemcp-admin-client/lib/discard-good-job.js';
61
61
  import { rescheduleGoodJob } from './pulsemcp-admin-client/lib/reschedule-good-job.js';
62
62
  import { forceTriggerGoodJobCron } from './pulsemcp-admin-client/lib/force-trigger-good-job-cron.js';
63
+ import { setPopularityDropBypass } from './pulsemcp-admin-client/lib/set-popularity-drop-bypass.js';
63
64
  import { cleanupGoodJobs } from './pulsemcp-admin-client/lib/cleanup-good-jobs.js';
64
65
  import { runExamForMirror } from './pulsemcp-admin-client/lib/run-exam-for-mirror.js';
65
66
  import { saveResultsForMirror } from './pulsemcp-admin-client/lib/save-results-for-mirror.js';
@@ -293,6 +294,9 @@ export class PulseMCPAdminClient {
293
294
  async forceTriggerGoodJobCron(cronKey) {
294
295
  return forceTriggerGoodJobCron(this.apiKey, this.baseUrl, cronKey);
295
296
  }
297
+ async setPopularityDropBypass(enabled) {
298
+ return setPopularityDropBypass(this.apiKey, this.baseUrl, enabled);
299
+ }
296
300
  async cleanupGoodJobs(params) {
297
301
  return cleanupGoodJobs(this.apiKey, this.baseUrl, params);
298
302
  }
@@ -48,6 +48,9 @@ const PARAM_DESCRIPTIONS = {
48
48
  github_owner: 'GitHub organization or username that owns the repository.',
49
49
  github_repo: 'GitHub repository name (without owner prefix).',
50
50
  github_subfolder: 'Subfolder path within the repository, for monorepos. Omit for root-level projects.',
51
+ // Package registry fields (UPDATE only)
52
+ package_registry: "(UPDATE ONLY, SERVER ONLY) Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.",
53
+ package_name: '(UPDATE ONLY, SERVER ONLY) Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem"). To CLEAR (unlink) the server\'s registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.',
51
54
  // Remote endpoints
52
55
  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.',
53
56
  // Canonical URLs
@@ -90,6 +93,9 @@ const SaveMCPImplementationSchema = z.object({
90
93
  github_owner: z.string().optional().describe(PARAM_DESCRIPTIONS.github_owner),
91
94
  github_repo: z.string().optional().describe(PARAM_DESCRIPTIONS.github_repo),
92
95
  github_subfolder: z.string().optional().describe(PARAM_DESCRIPTIONS.github_subfolder),
96
+ // Package registry fields (UPDATE only)
97
+ package_registry: z.string().optional().describe(PARAM_DESCRIPTIONS.package_registry),
98
+ package_name: z.string().optional().describe(PARAM_DESCRIPTIONS.package_name),
93
99
  // Remote endpoints
94
100
  remote: z
95
101
  .array(z.object({
@@ -208,6 +214,7 @@ Important notes:
208
214
  - \`classification\` and \`implementation_language\` only apply to servers
209
215
  - \`provider_name\` reuses existing provider if it matches a provider's slug
210
216
  - Setting mcp_server_id or mcp_client_id to null will unlink the association (UPDATE only)
217
+ - Registry package (UPDATE only): pass an empty string for BOTH \`package_registry\` and \`package_name\` to CLEAR (unlink) the link; passing only one as empty is rejected with a 422 error; omitting both leaves the link unchanged
211
218
  - Remote endpoints are for MCP servers only and configure how they can be accessed
212
219
  - Canonical URLs help identify the authoritative source for the implementation
213
220
  - After creating/updating, use \`get_mcp_server\` to verify the full state including remotes and canonical URLs`,
@@ -299,6 +306,15 @@ Important notes:
299
306
  type: 'string',
300
307
  description: PARAM_DESCRIPTIONS.github_subfolder,
301
308
  },
309
+ // Package registry fields (UPDATE only)
310
+ package_registry: {
311
+ type: 'string',
312
+ description: PARAM_DESCRIPTIONS.package_registry,
313
+ },
314
+ package_name: {
315
+ type: 'string',
316
+ description: PARAM_DESCRIPTIONS.package_name,
317
+ },
302
318
  // Remote endpoints
303
319
  remote: {
304
320
  type: 'array',
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ enabled: 'true enables the one-off bypass; false disables it. When enabled, the NEXT run of UpdatePopularityEstimatesFromBigqueryJob flushes the legitimate downward corrections currently held by the SYSTEMIC_DROP guardrail (and clears their popularity_drop_held_since), then the flag auto-resets to false so the bypass never silently stays on.',
4
+ };
5
+ const SetPopularityDropBypassSchema = z.object({
6
+ enabled: z.boolean().describe(PARAM_DESCRIPTIONS.enabled),
7
+ });
8
+ export function setPopularityDropBypass(_server, clientFactory) {
9
+ return {
10
+ name: 'set_popularity_drop_bypass',
11
+ description: `Enable or disable a deliberate, auditable ONE-OFF bypass of the SYSTEMIC_DROP guardrail in the popularity-estimates pipeline.
12
+
13
+ Background: UpdatePopularityEstimatesFromBigqueryJob holds large downward popularity corrections when more than ~10% of servers would drop at once (a possible systemic upstream event), keeping each flagged server at its stale value for ~3 days. During a known-legitimate remediation wave (e.g. correcting servers mis-linked to famous registry packages), that guardrail keeps genuinely-corrected servers visible at inflated values.
14
+
15
+ Enabling this bypass tells the NEXT job run to skip the SYSTEMIC_DROP hold for that single run only: it applies the corrected (dropped) BigQuery values, clears popularity_drop_held_since for the affected servers, then CONSUMES the flag (auto-resets enabled→false). The independent impossible-RISE guardrail is unaffected.
16
+
17
+ Returns the resulting bypass status (enabled / enabled_at / enabled_by).
18
+
19
+ Use cases:
20
+ - Enable the bypass before the next scheduled run to flush a known-legitimate correction wave caught in a systemic hold
21
+ - Disable the bypass if it was enabled in error and the next run has not yet consumed it`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ enabled: { type: 'boolean', description: PARAM_DESCRIPTIONS.enabled },
26
+ },
27
+ required: ['enabled'],
28
+ },
29
+ handler: async (args) => {
30
+ const validatedArgs = SetPopularityDropBypassSchema.parse(args);
31
+ const client = clientFactory();
32
+ try {
33
+ const result = await client.setPopularityDropBypass(validatedArgs.enabled);
34
+ const text = `Popularity drop bypass ${result.enabled ? 'ENABLED' : 'DISABLED'}.
35
+ - enabled: ${result.enabled}
36
+ - enabled_at: ${result.enabled_at ?? '(none)'}
37
+ - enabled_by: ${result.enabled_by ?? '(none)'}${result.enabled
38
+ ? '\n\nThe next UpdatePopularityEstimatesFromBigqueryJob run will flush held drops and then auto-reset this flag.'
39
+ : ''}`;
40
+ return { content: [{ type: 'text', text }] };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: `Error setting popularity drop bypass: ${error instanceof Error ? error.message : String(error)}`,
48
+ },
49
+ ],
50
+ isError: true,
51
+ };
52
+ }
53
+ },
54
+ };
55
+ }
@@ -13,8 +13,8 @@ const PARAM_DESCRIPTIONS = {
13
13
  provider_slug: 'URL slug for provider (auto-generated from name if omitted)',
14
14
  provider_url: 'Website URL for provider',
15
15
  source_code: 'GitHub repository information',
16
- package_registry: 'Package registry: npm, pypi, cargo, etc.',
17
- package_name: 'Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem")',
16
+ package_registry: "Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.",
17
+ package_name: 'Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem"). To CLEAR (unlink) the server\'s registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.',
18
18
  recommended: 'Mark this server as recommended by PulseMCP',
19
19
  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)',
20
20
  created_on_override: 'Override the automatically derived created date (ISO date string, e.g., "2025-01-15")',
@@ -176,6 +176,26 @@ To update an existing remote, include its ID:
176
176
  }
177
177
  \`\`\`
178
178
 
179
+ ## Updating / Clearing the Registry Package Link
180
+ Set the link by providing both fields:
181
+ \`\`\`json
182
+ {
183
+ "implementation_id": 456,
184
+ "package_registry": "npm",
185
+ "package_name": "@modelcontextprotocol/server-filesystem"
186
+ }
187
+ \`\`\`
188
+
189
+ To **clear** (unlink) the registry package, pass an empty string for **both** fields:
190
+ \`\`\`json
191
+ {
192
+ "implementation_id": 456,
193
+ "package_registry": "",
194
+ "package_name": ""
195
+ }
196
+ \`\`\`
197
+ Passing an empty string for only one of the two is rejected with a 422 error ("Package registry and package name must be provided together"); omitting both leaves the link unchanged.
198
+
179
199
  ## Linking/Creating Provider
180
200
  Link existing provider by ID:
181
201
  \`\`\`json
@@ -65,6 +65,7 @@ import { discardGoodJob } from './tools/discard-good-job.js';
65
65
  import { rescheduleGoodJob } from './tools/reschedule-good-job.js';
66
66
  import { forceTriggerGoodJobCron } from './tools/force-trigger-good-job-cron.js';
67
67
  import { cleanupGoodJobs } from './tools/cleanup-good-jobs.js';
68
+ import { setPopularityDropBypass } from './tools/set-popularity-drop-bypass.js';
68
69
  // Proctor tools
69
70
  import { runExamForMirror } from './tools/run-exam-for-mirror.js';
70
71
  import { getExamResult } from './tools/get-exam-result.js';
@@ -263,6 +264,10 @@ const ALL_TOOLS = [
263
264
  { factory: rescheduleGoodJob, groups: ['good_jobs'], isWriteOperation: true },
264
265
  { factory: forceTriggerGoodJobCron, groups: ['good_jobs'], isWriteOperation: true },
265
266
  { factory: cleanupGoodJobs, groups: ['good_jobs'], isWriteOperation: true },
267
+ // setPopularityDropBypass toggles a one-off bypass of the SYSTEMIC_DROP guardrail in
268
+ // UpdatePopularityEstimatesFromBigqueryJob. It lives in good_jobs because it controls the
269
+ // behavior of a background job and is exposed via the same write-enabled good_jobs credential.
270
+ { factory: setPopularityDropBypass, groups: ['good_jobs'], isWriteOperation: true },
266
271
  // Proctor tools
267
272
  { factory: runExamForMirror, groups: ['proctor'], isWriteOperation: true },
268
273
  { factory: getExamResult, groups: ['proctor'], isWriteOperation: false },
@@ -23,6 +23,6 @@
23
23
  "devDependencies": {
24
24
  "@types/node": "^24.10.12",
25
25
  "typescript": "^5.7.3",
26
- "vitest": "^3.2.3"
26
+ "vitest": "^4.1.8"
27
27
  }
28
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsemcp-cms-admin-mcp-server",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Local implementation of PulseMCP CMS Admin MCP server",
5
5
  "mcpName": "com.pulsemcp/pulsemcp-cms-admin",
6
6
  "main": "build/index.js",
@@ -0,0 +1,11 @@
1
+ import type { PopularityDropBypassStatus } from '../../types.js';
2
+ /**
3
+ * Enable or disable the one-off SYSTEMIC_DROP bypass for popularity estimates.
4
+ *
5
+ * POST /api/popularity_drop_bypass with body { enabled } sets the flag and
6
+ * returns the resulting status. When enabled, the NEXT run of
7
+ * UpdatePopularityEstimatesFromBigqueryJob flushes legitimate drops held by the
8
+ * SYSTEMIC_DROP guardrail, then consumes (auto-resets) the flag.
9
+ */
10
+ export declare function setPopularityDropBypass(apiKey: string, baseUrl: string, enabled: boolean): Promise<PopularityDropBypassStatus>;
11
+ //# sourceMappingURL=set-popularity-drop-bypass.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { adminFetch } from './admin-fetch.js';
2
+ /**
3
+ * Enable or disable the one-off SYSTEMIC_DROP bypass for popularity estimates.
4
+ *
5
+ * POST /api/popularity_drop_bypass with body { enabled } sets the flag and
6
+ * returns the resulting status. When enabled, the NEXT run of
7
+ * UpdatePopularityEstimatesFromBigqueryJob flushes legitimate drops held by the
8
+ * SYSTEMIC_DROP guardrail, then consumes (auto-resets) the flag.
9
+ */
10
+ export async function setPopularityDropBypass(apiKey, baseUrl, enabled) {
11
+ const url = new URL('/api/popularity_drop_bypass', baseUrl);
12
+ const response = await adminFetch(url.toString(), {
13
+ method: 'POST',
14
+ headers: {
15
+ 'X-API-Key': apiKey,
16
+ Accept: 'application/json',
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ body: JSON.stringify({ enabled }),
20
+ });
21
+ if (!response.ok) {
22
+ if (response.status === 401) {
23
+ throw new Error('Invalid API key');
24
+ }
25
+ if (response.status === 403) {
26
+ throw new Error('User lacks write privileges');
27
+ }
28
+ if (response.status === 404) {
29
+ throw new Error('Popularity drop bypass endpoint not found');
30
+ }
31
+ if (response.status === 422) {
32
+ const errorData = (await response.json().catch(() => ({})));
33
+ throw new Error(`Validation failed: ${errorData.errors?.join(', ') || 'Unknown error'}`);
34
+ }
35
+ throw new Error(`Failed to set popularity drop bypass: ${response.status} ${response.statusText}`);
36
+ }
37
+ return (await response.json());
38
+ }
@@ -940,6 +940,13 @@ export function createMockPulseMCPAdminClient(mockData) {
940
940
  deleted_count: 0,
941
941
  };
942
942
  },
943
+ async setPopularityDropBypass(enabled) {
944
+ return {
945
+ enabled,
946
+ enabled_at: enabled ? '2026-06-09T00:00:00Z' : null,
947
+ enabled_by: enabled ? 'mock-admin' : null,
948
+ };
949
+ },
943
950
  // Proctor methods
944
951
  async runExamForMirror() {
945
952
  return {
@@ -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, SetKnownMissingInitToolsListResponse } 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, PopularityDropBypassStatus, 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;
@@ -166,6 +166,7 @@ export interface IPulseMCPAdminClient {
166
166
  discardGoodJob(id: string): Promise<GoodJobActionResponse>;
167
167
  rescheduleGoodJob(id: string, scheduledAt: string): Promise<GoodJobActionResponse>;
168
168
  forceTriggerGoodJobCron(cronKey: string): Promise<GoodJobActionResponse>;
169
+ setPopularityDropBypass(enabled: boolean): Promise<PopularityDropBypassStatus>;
169
170
  cleanupGoodJobs(params?: {
170
171
  older_than_days?: number;
171
172
  status?: GoodJobStatus;
@@ -366,6 +367,7 @@ export declare class PulseMCPAdminClient implements IPulseMCPAdminClient {
366
367
  discardGoodJob(id: string): Promise<GoodJobActionResponse>;
367
368
  rescheduleGoodJob(id: string, scheduledAt: string): Promise<GoodJobActionResponse>;
368
369
  forceTriggerGoodJobCron(cronKey: string): Promise<GoodJobActionResponse>;
370
+ setPopularityDropBypass(enabled: boolean): Promise<PopularityDropBypassStatus>;
369
371
  cleanupGoodJobs(params?: {
370
372
  older_than_days?: number;
371
373
  status?: GoodJobStatus;
package/shared/server.js CHANGED
@@ -60,6 +60,7 @@ import { retryGoodJob } from './pulsemcp-admin-client/lib/retry-good-job.js';
60
60
  import { discardGoodJob } from './pulsemcp-admin-client/lib/discard-good-job.js';
61
61
  import { rescheduleGoodJob } from './pulsemcp-admin-client/lib/reschedule-good-job.js';
62
62
  import { forceTriggerGoodJobCron } from './pulsemcp-admin-client/lib/force-trigger-good-job-cron.js';
63
+ import { setPopularityDropBypass } from './pulsemcp-admin-client/lib/set-popularity-drop-bypass.js';
63
64
  import { cleanupGoodJobs } from './pulsemcp-admin-client/lib/cleanup-good-jobs.js';
64
65
  import { runExamForMirror } from './pulsemcp-admin-client/lib/run-exam-for-mirror.js';
65
66
  import { saveResultsForMirror } from './pulsemcp-admin-client/lib/save-results-for-mirror.js';
@@ -293,6 +294,9 @@ export class PulseMCPAdminClient {
293
294
  async forceTriggerGoodJobCron(cronKey) {
294
295
  return forceTriggerGoodJobCron(this.apiKey, this.baseUrl, cronKey);
295
296
  }
297
+ async setPopularityDropBypass(enabled) {
298
+ return setPopularityDropBypass(this.apiKey, this.baseUrl, enabled);
299
+ }
296
300
  async cleanupGoodJobs(params) {
297
301
  return cleanupGoodJobs(this.apiKey, this.baseUrl, params);
298
302
  }
@@ -91,6 +91,14 @@ export declare function saveMCPImplementation(_server: Server, clientFactory: Cl
91
91
  type: string;
92
92
  description: "Subfolder path within the repository, for monorepos. Omit for root-level projects.";
93
93
  };
94
+ package_registry: {
95
+ type: string;
96
+ description: "(UPDATE ONLY, SERVER ONLY) Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.";
97
+ };
98
+ package_name: {
99
+ type: string;
100
+ description: "(UPDATE ONLY, SERVER ONLY) Package name on the registry (e.g., \"@modelcontextprotocol/server-filesystem\"). To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.";
101
+ };
94
102
  remote: {
95
103
  type: string;
96
104
  items: {
@@ -48,6 +48,9 @@ const PARAM_DESCRIPTIONS = {
48
48
  github_owner: 'GitHub organization or username that owns the repository.',
49
49
  github_repo: 'GitHub repository name (without owner prefix).',
50
50
  github_subfolder: 'Subfolder path within the repository, for monorepos. Omit for root-level projects.',
51
+ // Package registry fields (UPDATE only)
52
+ package_registry: "(UPDATE ONLY, SERVER ONLY) Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.",
53
+ package_name: '(UPDATE ONLY, SERVER ONLY) Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem"). To CLEAR (unlink) the server\'s registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.',
51
54
  // Remote endpoints
52
55
  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.',
53
56
  // Canonical URLs
@@ -90,6 +93,9 @@ const SaveMCPImplementationSchema = z.object({
90
93
  github_owner: z.string().optional().describe(PARAM_DESCRIPTIONS.github_owner),
91
94
  github_repo: z.string().optional().describe(PARAM_DESCRIPTIONS.github_repo),
92
95
  github_subfolder: z.string().optional().describe(PARAM_DESCRIPTIONS.github_subfolder),
96
+ // Package registry fields (UPDATE only)
97
+ package_registry: z.string().optional().describe(PARAM_DESCRIPTIONS.package_registry),
98
+ package_name: z.string().optional().describe(PARAM_DESCRIPTIONS.package_name),
93
99
  // Remote endpoints
94
100
  remote: z
95
101
  .array(z.object({
@@ -208,6 +214,7 @@ Important notes:
208
214
  - \`classification\` and \`implementation_language\` only apply to servers
209
215
  - \`provider_name\` reuses existing provider if it matches a provider's slug
210
216
  - Setting mcp_server_id or mcp_client_id to null will unlink the association (UPDATE only)
217
+ - Registry package (UPDATE only): pass an empty string for BOTH \`package_registry\` and \`package_name\` to CLEAR (unlink) the link; passing only one as empty is rejected with a 422 error; omitting both leaves the link unchanged
211
218
  - Remote endpoints are for MCP servers only and configure how they can be accessed
212
219
  - Canonical URLs help identify the authoritative source for the implementation
213
220
  - After creating/updating, use \`get_mcp_server\` to verify the full state including remotes and canonical URLs`,
@@ -299,6 +306,15 @@ Important notes:
299
306
  type: 'string',
300
307
  description: PARAM_DESCRIPTIONS.github_subfolder,
301
308
  },
309
+ // Package registry fields (UPDATE only)
310
+ package_registry: {
311
+ type: 'string',
312
+ description: PARAM_DESCRIPTIONS.package_registry,
313
+ },
314
+ package_name: {
315
+ type: 'string',
316
+ description: PARAM_DESCRIPTIONS.package_name,
317
+ },
302
318
  // Remote endpoints
303
319
  remote: {
304
320
  type: 'array',
@@ -0,0 +1,30 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import type { ClientFactory } from '../server.js';
3
+ export declare function setPopularityDropBypass(_server: Server, clientFactory: ClientFactory): {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: string;
8
+ properties: {
9
+ enabled: {
10
+ type: string;
11
+ description: "true enables the one-off bypass; false disables it. When enabled, the NEXT run of UpdatePopularityEstimatesFromBigqueryJob flushes the legitimate downward corrections currently held by the SYSTEMIC_DROP guardrail (and clears their popularity_drop_held_since), then the flag auto-resets to false so the bypass never silently stays on.";
12
+ };
13
+ };
14
+ required: string[];
15
+ };
16
+ handler: (args: unknown) => Promise<{
17
+ content: {
18
+ type: string;
19
+ text: string;
20
+ }[];
21
+ isError?: undefined;
22
+ } | {
23
+ content: {
24
+ type: string;
25
+ text: string;
26
+ }[];
27
+ isError: boolean;
28
+ }>;
29
+ };
30
+ //# sourceMappingURL=set-popularity-drop-bypass.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ const PARAM_DESCRIPTIONS = {
3
+ enabled: 'true enables the one-off bypass; false disables it. When enabled, the NEXT run of UpdatePopularityEstimatesFromBigqueryJob flushes the legitimate downward corrections currently held by the SYSTEMIC_DROP guardrail (and clears their popularity_drop_held_since), then the flag auto-resets to false so the bypass never silently stays on.',
4
+ };
5
+ const SetPopularityDropBypassSchema = z.object({
6
+ enabled: z.boolean().describe(PARAM_DESCRIPTIONS.enabled),
7
+ });
8
+ export function setPopularityDropBypass(_server, clientFactory) {
9
+ return {
10
+ name: 'set_popularity_drop_bypass',
11
+ description: `Enable or disable a deliberate, auditable ONE-OFF bypass of the SYSTEMIC_DROP guardrail in the popularity-estimates pipeline.
12
+
13
+ Background: UpdatePopularityEstimatesFromBigqueryJob holds large downward popularity corrections when more than ~10% of servers would drop at once (a possible systemic upstream event), keeping each flagged server at its stale value for ~3 days. During a known-legitimate remediation wave (e.g. correcting servers mis-linked to famous registry packages), that guardrail keeps genuinely-corrected servers visible at inflated values.
14
+
15
+ Enabling this bypass tells the NEXT job run to skip the SYSTEMIC_DROP hold for that single run only: it applies the corrected (dropped) BigQuery values, clears popularity_drop_held_since for the affected servers, then CONSUMES the flag (auto-resets enabled→false). The independent impossible-RISE guardrail is unaffected.
16
+
17
+ Returns the resulting bypass status (enabled / enabled_at / enabled_by).
18
+
19
+ Use cases:
20
+ - Enable the bypass before the next scheduled run to flush a known-legitimate correction wave caught in a systemic hold
21
+ - Disable the bypass if it was enabled in error and the next run has not yet consumed it`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ enabled: { type: 'boolean', description: PARAM_DESCRIPTIONS.enabled },
26
+ },
27
+ required: ['enabled'],
28
+ },
29
+ handler: async (args) => {
30
+ const validatedArgs = SetPopularityDropBypassSchema.parse(args);
31
+ const client = clientFactory();
32
+ try {
33
+ const result = await client.setPopularityDropBypass(validatedArgs.enabled);
34
+ const text = `Popularity drop bypass ${result.enabled ? 'ENABLED' : 'DISABLED'}.
35
+ - enabled: ${result.enabled}
36
+ - enabled_at: ${result.enabled_at ?? '(none)'}
37
+ - enabled_by: ${result.enabled_by ?? '(none)'}${result.enabled
38
+ ? '\n\nThe next UpdatePopularityEstimatesFromBigqueryJob run will flush held drops and then auto-reset this flag.'
39
+ : ''}`;
40
+ return { content: [{ type: 'text', text }] };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: `Error setting popularity drop bypass: ${error instanceof Error ? error.message : String(error)}`,
48
+ },
49
+ ],
50
+ isError: true,
51
+ };
52
+ }
53
+ },
54
+ };
55
+ }
@@ -78,11 +78,11 @@ export declare function updateMCPServer(_server: Server, clientFactory: ClientFa
78
78
  };
79
79
  package_registry: {
80
80
  type: string;
81
- description: "Package registry: npm, pypi, cargo, etc.";
81
+ description: "Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.";
82
82
  };
83
83
  package_name: {
84
84
  type: string;
85
- description: "Package name on the registry (e.g., \"@modelcontextprotocol/server-filesystem\")";
85
+ description: "Package name on the registry (e.g., \"@modelcontextprotocol/server-filesystem\"). To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.";
86
86
  };
87
87
  recommended: {
88
88
  type: string;
@@ -13,8 +13,8 @@ const PARAM_DESCRIPTIONS = {
13
13
  provider_slug: 'URL slug for provider (auto-generated from name if omitted)',
14
14
  provider_url: 'Website URL for provider',
15
15
  source_code: 'GitHub repository information',
16
- package_registry: 'Package registry: npm, pypi, cargo, etc.',
17
- package_name: 'Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem")',
16
+ package_registry: "Package registry: npm, pypi, cargo, etc. To CLEAR (unlink) the server's registry package, pass an empty string for BOTH `package_registry` and `package_name`. Passing an empty string for only one of the two is rejected with a 422 error.",
17
+ package_name: 'Package name on the registry (e.g., "@modelcontextprotocol/server-filesystem"). To CLEAR (unlink) the server\'s registry package, pass an empty string for BOTH `package_name` and `package_registry`. Passing an empty string for only one of the two is rejected with a 422 error.',
18
18
  recommended: 'Mark this server as recommended by PulseMCP',
19
19
  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)',
20
20
  created_on_override: 'Override the automatically derived created date (ISO date string, e.g., "2025-01-15")',
@@ -176,6 +176,26 @@ To update an existing remote, include its ID:
176
176
  }
177
177
  \`\`\`
178
178
 
179
+ ## Updating / Clearing the Registry Package Link
180
+ Set the link by providing both fields:
181
+ \`\`\`json
182
+ {
183
+ "implementation_id": 456,
184
+ "package_registry": "npm",
185
+ "package_name": "@modelcontextprotocol/server-filesystem"
186
+ }
187
+ \`\`\`
188
+
189
+ To **clear** (unlink) the registry package, pass an empty string for **both** fields:
190
+ \`\`\`json
191
+ {
192
+ "implementation_id": 456,
193
+ "package_registry": "",
194
+ "package_name": ""
195
+ }
196
+ \`\`\`
197
+ Passing an empty string for only one of the two is rejected with a 422 error ("Package registry and package name must be provided together"); omitting both leaves the link unchanged.
198
+
179
199
  ## Linking/Creating Provider
180
200
  Link existing provider by ID:
181
201
  \`\`\`json
package/shared/tools.js CHANGED
@@ -65,6 +65,7 @@ import { discardGoodJob } from './tools/discard-good-job.js';
65
65
  import { rescheduleGoodJob } from './tools/reschedule-good-job.js';
66
66
  import { forceTriggerGoodJobCron } from './tools/force-trigger-good-job-cron.js';
67
67
  import { cleanupGoodJobs } from './tools/cleanup-good-jobs.js';
68
+ import { setPopularityDropBypass } from './tools/set-popularity-drop-bypass.js';
68
69
  // Proctor tools
69
70
  import { runExamForMirror } from './tools/run-exam-for-mirror.js';
70
71
  import { getExamResult } from './tools/get-exam-result.js';
@@ -263,6 +264,10 @@ const ALL_TOOLS = [
263
264
  { factory: rescheduleGoodJob, groups: ['good_jobs'], isWriteOperation: true },
264
265
  { factory: forceTriggerGoodJobCron, groups: ['good_jobs'], isWriteOperation: true },
265
266
  { factory: cleanupGoodJobs, groups: ['good_jobs'], isWriteOperation: true },
267
+ // setPopularityDropBypass toggles a one-off bypass of the SYSTEMIC_DROP guardrail in
268
+ // UpdatePopularityEstimatesFromBigqueryJob. It lives in good_jobs because it controls the
269
+ // behavior of a background job and is exposed via the same write-enabled good_jobs credential.
270
+ { factory: setPopularityDropBypass, groups: ['good_jobs'], isWriteOperation: true },
266
271
  // Proctor tools
267
272
  { factory: runExamForMirror, groups: ['proctor'], isWriteOperation: true },
268
273
  { factory: getExamResult, groups: ['proctor'], isWriteOperation: false },
package/shared/types.d.ts CHANGED
@@ -897,6 +897,11 @@ export interface SetKnownMissingInitToolsListResponse {
897
897
  known_missing_init_tools_list: boolean;
898
898
  known_missing_init_tools_list_filter_to: string | null;
899
899
  }
900
+ export interface PopularityDropBypassStatus {
901
+ enabled: boolean;
902
+ enabled_at: string | null;
903
+ enabled_by: string | null;
904
+ }
900
905
  export interface MozMetrics {
901
906
  page_authority?: number;
902
907
  domain_authority?: number;