Skip to content

Conversation

@chelojimenez
Copy link
Contributor

@chelojimenez chelojimenez commented Nov 26, 2025

Based on modelcontextprotocol/registry#756
image


Note

Adds multi-registry support with a selector and add dialog, per-registry caching, and auth-required handling across client and server.

  • UI/UX (client)
    • RegistryTab: integrates RegistrySelector, per-registry refresh, and an auth-required empty state; updates header/status UI.
    • New registry/RegistrySelector with add/remove sources and auth indicator; opens AddRegistryDialog.
    • New registry/AddRegistryDialog to add custom registries (validates URL, checks connectivity/auth, updates store).
  • State Management
    • New stores/registry/registry-sources-store: persistable list of RegistrySources with active source management.
    • stores/registry/registry-store: per-registry cache keys, currentRegistryUrl, authRequired; fetch methods accept registryUrl, handle 401 auth responses, and cache per registry.
  • Client API
    • lib/registry-api: requests accept registryUrl/accessToken; isAuthRequired helper; all endpoints return typed auth-required responses on 401.
  • Server API
    • server/routes/mcp/registry: accept registryUrl param, forward Authorization, and return { requiresAuth, wwwAuthenticate, registryUrl } on 401 for servers, versions, and specific version endpoints.
  • Types
    • shared/types: add RegistrySource and RegistryAuthRequiredResponse types.

Written by Cursor Bugbot for commit 01d6e36. This will update automatically on new commits. Configure here.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Nov 26, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

This pull request introduces multi-registry support with authentication handling. New components enable users to select from and add custom registries. A Zustand-based store manages registry sources and state. The registry API layer now detects authentication requirements and surfaces them as distinct responses rather than throwing errors. The registry store supports per-registry caching and URL awareness. Server endpoints now accept optional registry URLs and forward authorization headers, returning authentication challenges when required. The RegistryTab component integrates the new registry selector and displays authentication prompts when needed.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
client/src/components/registry/AddRegistryDialog.tsx (1)

61-65: URL comparison may miss duplicates with trailing slash variance.

new URL(url).toString() preserves trailing slashes, so https://registry.example.com and https://registry.example.com/ are treated as distinct. Consider normalizing before comparison.

-      const existingSource = sources.find((s) => s.url === registryUrl.toString());
+      const normalizedUrl = registryUrl.toString().replace(/\/+$/, '');
+      const existingSource = sources.find((s) => s.url.replace(/\/+$/, '') === normalizedUrl);
client/src/stores/registry/registry-sources-store.ts (1)

72-76: Consider adding persist migration for future-proofing.

The store uses zustand/middleware persist correctly. For resilience against schema evolution, consider adding a version and migrate function to the persist config. This prevents stale localStorage data from causing issues if RegistrySource gains new required fields.

     {
       name: "mcp-registry-sources",
+      version: 1,
+      migrate: (persistedState, version) => {
+        // Handle future migrations here
+        return persistedState as RegistrySourcesState;
+      },
     }
client/src/components/RegistryTab.tsx (2)

184-184: Dependency array includes allServers but body uses latestServers.

The filteredServers memo depends on allServers in its dependency array, yet the filtering logic operates on latestServers. This is functionally correct since latestServers derives from allServers, but the explicit dependency should be latestServers for clarity.

-  }, [allServers, searchQuery, fuseInstance]);
+  }, [latestServers, searchQuery, fuseInstance]);

31-34: Inline selector duplicates getActiveSource() logic.

The store exposes getActiveSource(), yet here we inline equivalent logic. Consider using the store method for consistency, though this inline approach avoids a function call on each render.

server/routes/mcp/registry.ts (1)

36-47: Consider extracting the 401 response handling into a helper.

The identical 401 handling pattern appears thrice. A small helper would reduce duplication and ensure consistency:

function authRequiredResponse(c: Context, wwwAuthenticate: string | null, registryUrl: string) {
  return c.json({ requiresAuth: true, wwwAuthenticate, registryUrl }, 401);
}

Also applies to: 80-90, 125-135

client/src/stores/registry/registry-store.ts (1)

191-196: Closure captures registryChanged before async IIFE executes.

The registryChanged boolean is captured at function start, but the async IIFE runs later. If multiple fetchAllPages calls occur rapidly, the captured value may not reflect current state when the error handler executes at line 312.

Consider re-checking against current state inside the error handler:

-          if (currentState.allServers.length > 0 && !registryChanged) {
+          const stillSameRegistry = currentState.currentRegistryUrl === targetUrl;
+          if (currentState.allServers.length > 0 && stillSameRegistry) {
client/src/lib/registry-api.ts (1)

29-32: Consider extracting header construction into a shared helper.

The Authorization header logic repeats across all three functions:

function buildHeaders(accessToken?: string): HeadersInit {
  const headers: HeadersInit = {};
  if (accessToken) {
    headers["Authorization"] = `Bearer ${accessToken}`;
  }
  return headers;
}

This mirrors the server-side getRegistryHeaders pattern and would reduce duplication.

Also applies to: 69-72, 101-104

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4093e1 and 01d6e36.

📒 Files selected for processing (8)
  • client/src/components/RegistryTab.tsx (5 hunks)
  • client/src/components/registry/AddRegistryDialog.tsx (1 hunks)
  • client/src/components/registry/RegistrySelector.tsx (1 hunks)
  • client/src/lib/registry-api.ts (3 hunks)
  • client/src/stores/registry/registry-sources-store.ts (1 hunks)
  • client/src/stores/registry/registry-store.ts (13 hunks)
  • server/routes/mcp/registry.ts (3 hunks)
  • shared/types.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
client/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (client/CLAUDE.md)

client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support

Files:

  • client/src/components/registry/AddRegistryDialog.tsx
  • client/src/components/registry/RegistrySelector.tsx
  • client/src/components/RegistryTab.tsx
client/**/{components,hooks}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (client/CLAUDE.md)

Use React hooks (useState, useReducer) for local state management with proper effect cleanup

Files:

  • client/src/components/registry/AddRegistryDialog.tsx
  • client/src/components/registry/RegistrySelector.tsx
  • client/src/components/RegistryTab.tsx
client/**/stores/**/*.{ts,tsx}

📄 CodeRabbit inference engine (client/CLAUDE.md)

Use Zustand for global state management with proper store creation, action definitions, selector optimization, and middleware usage

Files:

  • client/src/stores/registry/registry-sources-store.ts
  • client/src/stores/registry/registry-store.ts
server/**/*.ts

📄 CodeRabbit inference engine (server/CLAUDE.md)

Implement TypeScript for type safety throughout the codebase

Files:

  • server/routes/mcp/registry.ts
🧠 Learnings (14)
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips

Applied to files:

  • client/src/components/registry/AddRegistryDialog.tsx
  • client/src/components/registry/RegistrySelector.tsx
  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/**/*.{ts,tsx} : Use Zustand for global state management with proper store creation, action definitions, selector optimization, and middleware usage

Applied to files:

  • client/src/stores/registry/registry-sources-store.ts
  • client/src/stores/registry/registry-store.ts
  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state

Applied to files:

  • client/src/stores/registry/registry-sources-store.ts
  • client/src/stores/registry/registry-store.ts
  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{stores/servers,lib/api}/**/*.{ts,tsx} : Persist server configurations with local storage, export/import functionality, sync options, and backup/restore capabilities

Applied to files:

  • client/src/stores/registry/registry-sources-store.ts
  • client/src/stores/registry/registry-store.ts
  • client/src/lib/registry-api.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/chat/**/*.{ts,tsx} : Implement AI model state handling including model selection state, generation parameters, stream management, and history persistence

Applied to files:

  • client/src/stores/registry/registry-sources-store.ts
📚 Learning: 2025-11-24T17:54:18.310Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.310Z
Learning: Use Zustand for lightweight, flexible global state management

Applied to files:

  • client/src/stores/registry/registry-sources-store.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Provide transport protocol selection UI with protocol options, configuration forms, validation rules, and default presets

Applied to files:

  • client/src/components/registry/RegistrySelector.tsx
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files

Applied to files:

  • server/routes/mcp/registry.ts
  • client/src/lib/registry-api.ts
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/{app,index,routes/**}.ts : Use Hono.js for API routing and middleware in the backend

Applied to files:

  • server/routes/mcp/registry.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{components/servers,stores/servers}/**/*.{ts,tsx} : Implement authentication setup with OAuth configuration, token management, scope selection, and refresh handling

Applied to files:

  • client/src/stores/registry/registry-store.ts
  • client/src/lib/registry-api.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features

Applied to files:

  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{components,hooks}/**/*.{ts,tsx} : Use React hooks (useState, useReducer) for local state management with proper effect cleanup

Applied to files:

  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Use functional React components with React.FC typing

Applied to files:

  • client/src/components/RegistryTab.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management

Applied to files:

  • client/src/components/RegistryTab.tsx
🧬 Code graph analysis (4)
client/src/stores/registry/registry-sources-store.ts (1)
shared/types.ts (1)
  • RegistrySource (815-821)
server/routes/mcp/registry.ts (1)
bin/start.js (1)
  • url (606-606)
client/src/stores/registry/registry-store.ts (2)
shared/types.ts (1)
  • RegistryServer (760-782)
client/src/lib/registry-api.ts (2)
  • listRegistryServers (18-47)
  • isAuthRequired (52-56)
client/src/lib/registry-api.ts (1)
shared/types.ts (4)
  • RegistryServerListResponse (799-805)
  • RegistryAuthRequiredResponse (823-827)
  • RegistryVersionListResponse (807-812)
  • RegistryServerVersion (784-792)
🔍 Remote MCP

Summary: Additional Context for PR #974 - Private Registries Support

Based on my research, here's the relevant context and background information for reviewing this pull request:

MCP Registry Ecosystem Context

The Registry API has entered an API freeze (v0.1) in October 2025, with the API remaining stable with no breaking changes for at least the next month, allowing integrators to confidently implement support. Private subregistries will exist within enterprises that have strict privacy and security requirements, but the MCP Registry gives these enterprises a single upstream data source they can build upon, aiming to share API schemas with these private implementations so that associated SDKs and tooling can be shared across the ecosystem.

Architecture for Private Registries

The registry embraces federation where the upstream MCP Registry is not the only registry, but rather serves as the canonical source of public MCP server metadata that subregistries (public or private) can ingest, augment, or mirror. Private subregistries within enterprises can combine published servers with internal ones, apply custom policies, and share a consistent API surface with MCP clients, with subregistries expected to reuse the OpenAPI schema and metadata contracts defined by the upstream registry.

Authentication Handling

The registry stores metadata about server endpoints (including transport protocol support, authentication requirements, etc.), and server metadata may include declarations of required authentication headers or credentials, so clients know in advance which auth scheme to use. This aligns with the PR's RegistryAuthRequiredResponse type and authentication detection logic.

API Design Principles

The MCP Registry API is designed with "Reusability: API shapes designed for reuse, supporting private/internal registries" as a core principle. The API shapes and data formats are intentionally designed for reuse by subregistries.

Expected Client Behavior

It's highly recommended that clients use a subregistry rather than fetching data from the official registry directly, and clients might want to make this configurable so that users of the client can choose their preferred registry, as some enterprise users may have their own registry.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (18)
shared/types.ts (1)

814-827: Well-structured type definitions for multi-registry support.

The RegistrySource interface cleanly captures registry metadata, and RegistryAuthRequiredResponse employs a literal true type for requiresAuth, enabling precise type narrowing via the isAuthRequired type guard in registry-api.ts.

client/src/components/registry/AddRegistryDialog.tsx (2)

112-116: Enter-to-submit from name field is reasonable UX.

The handleKeyDown listener triggers submission from both inputs when Enter is pressed and a URL exists. This is intuitive behavior for a two-field form.


25-110: Clean implementation with proper state management and error handling.

The component follows React 19 patterns with hooks, integrates Radix UI Dialog per coding guidelines, and provides clear user feedback through toast notifications. The async flow handles validation, duplicate detection, and auth requirement discovery elegantly.

client/src/stores/registry/registry-sources-store.ts (1)

25-71: Solid Zustand store with appropriate immutability guards.

The store safeguards the official registry from modification or removal, uses crypto.randomUUID() for IDs, and gracefully falls back when the active source is removed. The action signatures are well-typed.

client/src/components/RegistryTab.tsx (2)

397-428: Auth-required state renders a clear, actionable UI.

The Lock icon and messaging communicate authentication needs effectively. Including RegistrySelector in the header ensures users can switch registries without navigating away.


207-217: Registry-aware refresh and change handlers are correctly wired.

handleRefresh passes the active registry URL, and handleRegistryChange uses useCallback with appropriate dependencies. This ensures registry context flows through data fetching.

client/src/components/registry/RegistrySelector.tsx (3)

78-91: Interactive element within SelectItem may cause focus/click conflicts.

Placing a Button inside a SelectItem can lead to unexpected behavior—clicking the trash icon might also trigger item selection despite stopPropagation. Test thoroughly, and consider moving delete actions to a separate context menu if issues arise.


96-101: Magic value "__add__" is safe given UUID-based source IDs.

Since addSource generates UUIDs, collision with "__add__" is impossible. This pattern cleanly separates the "add" action from actual registry sources.


24-50: Selector integrates cleanly with registry sources store.

State management follows Zustand patterns, and the onRegistryChange callback propagates URL changes to parent components. Event handling in handleRemoveSource correctly prevents bubbling.

server/routes/mcp/registry.ts (2)

5-16: Well-structured helper and constant extraction.

The getRegistryHeaders helper elegantly centralizes header construction, and extracting DEFAULT_REGISTRY_URL as a constant promotes maintainability.


49-54: Appropriate error handling for non-401 failures.

The throw-and-catch pattern preserves the original behavior while the new 401 logic integrates cleanly above it.

client/src/stores/registry/registry-store.ts (3)

103-117: Thoughtful initialization with cache preloading.

Loading cached data for the default registry on store creation provides an excellent UX—users see data immediately while fresh data loads in the background. The fallback to set currentRegistryUrl even without cache is correct.


138-146: Clean auth-required detection flow.

The early return upon isAuthRequired check prevents further processing and correctly sets authRequired state. This integrates well with the type guard from registry-api.ts.


280-301: Solid deduplication and cache persistence.

Using a Map keyed by ${name}@${version} for deduplication is efficient. Persisting to localStorage per-registry enables offline resilience. The state update is atomic and comprehensive.

client/src/lib/registry-api.ts (4)

10-13: Clean interface for request options.

RegistryRequestOptions provides a reusable contract for registry-aware API calls. The optional fields allow flexible composition.


52-56: Elegant type guard implementation.

The isAuthRequired function serves as a proper TypeScript type guard, enabling callers to narrow the union type cleanly. Checking both "requiresAuth" in response and the value being true is defensive and correct.


37-40: Consistent 401 handling across all API functions.

Returning the parsed RegistryAuthRequiredResponse instead of throwing enables callers to handle authentication challenges gracefully. The pattern is uniform and aligns with the shared type definition.

Also applies to: 77-79, 109-111


61-86: Signature change verified as backward-compatible.

The function has one existing call site in client/src/components/registry/ServerDetailModal.tsx (line 68), which calls listServerVersions(initialServer.name) with only the serverName argument. Since the new options parameter is optional, this call site remains compatible with the updated signature. No unexpected arguments are being passed, and no breaking changes exist.

Comment on lines +12 to +21
function getCacheKey(registryUrl: string): string {
// Use a hash-like identifier for the registry URL
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_KEY_PREFIX}-${urlId}`;
}

function getCacheTimestampKey(registryUrl: string): string {
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_TIMESTAMP_KEY_PREFIX}-${urlId}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

btoa() may throw for URLs containing non-ASCII characters.

Registry URLs with internationalized domain names or encoded paths could contain characters outside the Latin1 range, causing btoa() to throw.

 function getCacheKey(registryUrl: string): string {
-  const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
+  // Use encodeURIComponent to safely handle Unicode before base64 encoding
+  const urlId = btoa(encodeURIComponent(registryUrl))
+    .replace(/[^a-zA-Z0-9]/g, "")
+    .slice(0, 32);
   return `${CACHE_KEY_PREFIX}-${urlId}`;
 }

Also consider increasing the slice length to reduce collision probability across registries.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getCacheKey(registryUrl: string): string {
// Use a hash-like identifier for the registry URL
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_KEY_PREFIX}-${urlId}`;
}
function getCacheTimestampKey(registryUrl: string): string {
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_TIMESTAMP_KEY_PREFIX}-${urlId}`;
}
function getCacheKey(registryUrl: string): string {
// Use encodeURIComponent to safely handle Unicode before base64 encoding
const urlId = btoa(encodeURIComponent(registryUrl))
.replace(/[^a-zA-Z0-9]/g, "")
.slice(0, 32);
return `${CACHE_KEY_PREFIX}-${urlId}`;
}
function getCacheTimestampKey(registryUrl: string): string {
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_TIMESTAMP_KEY_PREFIX}-${urlId}`;
}

Comment on lines +23 to +25
const registryUrl = c.req.query("registryUrl") || DEFAULT_REGISTRY_URL;

let url = `${REGISTRY_BASE_URL}/servers?limit=${limit}`;
let url = `${registryUrl}/servers?limit=${limit}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential SSRF vulnerability with user-supplied registry URL.

The registryUrl query parameter flows directly into fetch() without validation. A malicious actor could probe internal services (e.g., http://localhost:6379, http://169.254.169.254/).

Consider an allowlist or URL validation:

+const ALLOWED_REGISTRY_HOSTS = new Set([
+  "registry.modelcontextprotocol.io",
+  // Add other trusted hosts
+]);
+
+function isValidRegistryUrl(url: string): boolean {
+  try {
+    const parsed = new URL(url);
+    return ALLOWED_REGISTRY_HOSTS.has(parsed.hostname) || 
+           parsed.hostname.endsWith(".modelcontextprotocol.io");
+  } catch {
+    return false;
+  }
+}

Then validate before use:

 const registryUrl = c.req.query("registryUrl") || DEFAULT_REGISTRY_URL;
+if (!isValidRegistryUrl(registryUrl)) {
+  return c.json({ success: false, error: "Invalid registry URL" }, 400);
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/routes/mcp/registry.ts around lines 23–25, the code uses the
user-supplied registryUrl directly to build an outbound URL which can enable
SSRF; instead, parse and validate registryUrl before use and fall back to
DEFAULT_REGISTRY_URL on failure. Implement one of: (preferred) an allowlist of
permitted hostnames/domains and only accept registryUrl whose hostname matches
the allowlist and whose protocol is http or https; or (minimum) parse the URL
and reject any with disallowed hosts/ranges (localhost, 127.0.0.0/8, ::1,
link-local, 169.254.0.0/16, metadata IPs) and non-http(s) schemes. If validation
fails, use DEFAULT_REGISTRY_URL; also ensure proper URL encoding when appending
query params.

function getCacheTimestampKey(registryUrl: string): string {
const urlId = btoa(registryUrl).replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
return `${CACHE_TIMESTAMP_KEY_PREFIX}-${urlId}`;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cache key collision risk with truncated URLs

The cache key generation truncates base64-encoded registry URLs to only 20 characters, creating a high collision risk between different registry URLs. When multiple private registries are added with similar URLs, their cache keys could collide, causing one registry's cached data to overwrite another's. This defeats the purpose of per-registry caching and can show incorrect server lists when switching between registries.

Fix in Cursor Fix in Web

e.preventDefault();
e.stopPropagation();
removeSource(sourceId);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Registry data not refreshed when removing active source

When removing the currently active registry source, the store correctly resets to the official registry, but handleRemoveSource doesn't call onRegistryChange to notify the parent component. This leaves the UI displaying stale data from the removed registry instead of fetching and showing servers from the official registry that's now active.

Fix in Cursor Fix in Web

authRequired: true,
error: "Authentication required for this registry",
});
return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Registry URL not updated on auth failure

In fetchServers, when authentication is required or an error occurs, currentRegistryUrl isn't updated to reflect the target registry. This causes subsequent calls without an explicit registryUrl parameter to use the wrong registry, and breaks registry change detection logic that compares against currentRegistryUrl. The same function correctly updates this field on successful responses.

Additional Locations (1)

Fix in Cursor Fix in Web

@matteo8p matteo8p force-pushed the add-private-registries branch from 01d6e36 to ff5fe45 Compare December 30, 2025 22:41
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Dec 30, 2025
@matteo8p matteo8p closed this Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants