Skip to content

Add MCP catalog#28

Merged
ScriptSmith merged 5 commits intomainfrom
mcp-catalog
Apr 21, 2026
Merged

Add MCP catalog#28
ScriptSmith merged 5 commits intomainfrom
mcp-catalog

Conversation

@ScriptSmith
Copy link
Copy Markdown
Owner

No description provided.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 20, 2026

Greptile Summary

This PR adds an MCP server catalog to the MCPConfigModal, backed by a new Rust config section (ui.mcp.favorites) and a TypeScript client for the public MCP Registry v0.1 API. Users can browse remote and local MCP servers, search the registry, and pre-fill the add-server form from catalog entries.

  • P1 security: buildInstallCommand embeds --cors \"*\" in the generated supergateway command, meaning any website the user is browsing can make cross-origin requests to the locally-running MCP proxy at localhost:8000. Local MCP servers often hold API keys or filesystem access, making this a real exfiltration surface. Consider scoping the CORS origin to window.location.origin instead.

Confidence Score: 4/5

Safe to merge after addressing the wildcard CORS issue in the supergateway install command.

The PR is well-structured with good test coverage on the Rust side and a clean React component architecture. Most issues raised in prior review rounds are addressed (shell injection fixed with single-quote escaping, loadingMore stuck state fixed with unconditional finally reset, required headers now emit placeholders). One new P1 security finding remains: --cors "*" in the generated supergateway command exposes the local server to all web origins.

ui/src/services/mcpRegistry/client.ts — buildInstallCommand CORS wildcard

Security Review

  • Wildcard CORS on local MCP proxy (ui/src/services/mcpRegistry/client.ts:190): The generated npx supergateway command includes --cors \"*\", which causes the proxy to serve Access-Control-Allow-Origin: *. Any website visited while the proxy is running can send arbitrary requests to http://localhost:8000/mcp and read responses — a cross-origin data exfiltration risk given local MCP servers can hold API keys or have filesystem access.

Important Files Changed

Filename Overview
ui/src/services/mcpRegistry/client.ts New registry client: search/lookup, install-command builder, header materializer. buildInstallCommand emits --cors "*" in the supergateway wrapper, exposing the local server to all web origins. Shell-injection and required-header-placeholder issues from previous threads are now addressed.
ui/src/components/MCPConfigModal/MCPCatalog.tsx New catalog component: favorites section, registry search with pagination, RemoteCard/LocalCard/FavoriteCard rendering. Load-more abort and loadingMore reset issues from prior threads are now fixed. React.ReactNode type references without the React namespace import remain (flagged in previous thread).
src/config/ui.rs Adds McpUiConfig and FavoriteMcpServer Rust structs with deny_unknown_fields, Default, and a set of well-known favorite servers. Clean and consistent with the rest of the config module.
src/routes/admin/ui_config.rs Adds McpUiResponse and FavoriteMcpServerResponse types plus From impls; wires them into the existing UiConfigResponse. Straightforward mapping, well-covered by the new integration tests.
ui/src/components/MCPConfigModal/MCPConfigModal.tsx Wires MCPCatalog into the modal's view state machine (list → catalog → form). The catalog-prefill/form-origin flow is handled correctly; favorites is read from config and passed through.

Sequence Diagram

sequenceDiagram
    participant User
    participant MCPConfigModal
    participant MCPCatalog
    participant RegistryAPI as MCP Registry API
    participant ServerForm
    participant LocalProxy as Local Supergateway Proxy

    User->>MCPConfigModal: Click "Add Server"
    MCPConfigModal->>MCPCatalog: show(favorites)
    MCPCatalog->>RegistryAPI: searchRegistry({limit:30})
    RegistryAPI-->>MCPCatalog: {servers[], nextCursor}
    MCPCatalog->>MCPCatalog: dedupeLatest() + categorize()

    alt Remote server selected
        User->>MCPCatalog: Click "Add"
        MCPCatalog->>ServerForm: onPick(CatalogPrefill{url, headers, authType})
    else Local/stdio server selected
        User->>MCPCatalog: Click "Set up"
        MCPCatalog->>ServerForm: onPick(CatalogPrefill{url=localhost:8000, localInstall.command})
        Note over ServerForm: Shows install banner with npx supergateway command
        User->>LocalProxy: Runs generated command locally
    else Favorite (registry ID)
        MCPCatalog->>RegistryAPI: getRegistryEntry(registryId)
        RegistryAPI-->>MCPCatalog: MCPRegistryEntry
        MCPCatalog->>MCPCatalog: categorize() → RemoteCard or LocalCard
    end

    ServerForm->>MCPConfigModal: onSubmit(values)
    MCPConfigModal->>MCPConfigModal: addServer()
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/services/mcpRegistry/client.ts
Line: 190

Comment:
**`--cors "*"` exposes the local MCP server to every web origin**

The generated supergateway command passes `--cors "*"`, which instructs supergateway to respond with `Access-Control-Allow-Origin: *`. While the server binds to `localhost`, CORS headers are what the *browser* enforces — any website the user visits while this proxy is running can send `fetch("http://localhost:8000/mcp", ...)` requests and read the responses. Local MCP servers routinely hold API keys in environment variables and may have filesystem or shell access, making this a meaningful cross-site exfiltration surface.

Consider narrowing the CORS origin to the gateway's own origin so only the Hadrian UI can reach the proxy. Since `buildInstallCommand` doesn't know the UI origin at call time, one approach is to derive the origin from `window.location.origin` and embed it:

```ts
const origin = window.location.origin;
command: `npx -y supergateway --cors "${origin}" --outputTransport streamableHttp --stdio ${quoted}`,
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "Review fixes" | Re-trigger Greptile

Comment thread ui/src/components/MCPConfigModal/MCPCatalog.tsx
Comment on lines +438 to +443
title: string;
icon: React.ReactNode;
hint: string;
footer?: React.ReactNode;
children: React.ReactNode;
}) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 React.ReactNode referenced without a React namespace import

Section's prop types reference React.ReactNode (lines 440, 441, 442), but the file only has named React-hook imports (import { useEffect, useRef, useState } from "react"). With the new JSX transform, React doesn't need to be in scope for JSX, but React.ReactNode as a type still requires either import React from "react" or the type written as ReactNode with import type { ReactNode } from "react". This will cause a TypeScript compile error in standard modern React project configurations.

Suggested change
title: string;
icon: React.ReactNode;
hint: string;
footer?: React.ReactNode;
children: React.ReactNode;
}) {
import { type ReactNode, useEffect, useRef, useState } from "react";

Then update the three type annotations to use ReactNode instead of React.ReactNode.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/MCPConfigModal/MCPCatalog.tsx
Line: 438-443

Comment:
**`React.ReactNode` referenced without a React namespace import**

`Section`'s prop types reference `React.ReactNode` (lines 440, 441, 442), but the file only has named React-hook imports (`import { useEffect, useRef, useState } from "react"`). With the new JSX transform, React doesn't need to be in scope for JSX, but `React.ReactNode` as a type still requires either `import React from "react"` or the type written as `ReactNode` with `import type { ReactNode } from "react"`. This will cause a TypeScript compile error in standard modern React project configurations.

```suggestion
import { type ReactNode, useEffect, useRef, useState } from "react";
```

Then update the three type annotations to use `ReactNode` instead of `React.ReactNode`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread ui/src/services/mcpRegistry/client.ts
Comment thread ui/src/services/mcpRegistry/client.ts
Comment thread ui/src/services/mcpRegistry/client.ts
@ScriptSmith
Copy link
Copy Markdown
Owner Author

@greptile-apps

Comment thread ui/src/components/MCPConfigModal/MCPCatalog.tsx
@ScriptSmith
Copy link
Copy Markdown
Owner Author

@greptile-apps

// closing the quote, inserting an escaped quote, and reopening: `'\''`.
const quoted = `'${raw.replace(/'/g, "'\\''")}'`;
return {
command: `npx -y supergateway --cors "*" --outputTransport streamableHttp --stdio ${quoted}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security --cors "*" exposes the local MCP server to every web origin

The generated supergateway command passes --cors "*", which instructs supergateway to respond with Access-Control-Allow-Origin: *. While the server binds to localhost, CORS headers are what the browser enforces — any website the user visits while this proxy is running can send fetch("http://localhost:8000/mcp", ...) requests and read the responses. Local MCP servers routinely hold API keys in environment variables and may have filesystem or shell access, making this a meaningful cross-site exfiltration surface.

Consider narrowing the CORS origin to the gateway's own origin so only the Hadrian UI can reach the proxy. Since buildInstallCommand doesn't know the UI origin at call time, one approach is to derive the origin from window.location.origin and embed it:

const origin = window.location.origin;
command: `npx -y supergateway --cors "${origin}" --outputTransport streamableHttp --stdio ${quoted}`,
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/services/mcpRegistry/client.ts
Line: 190

Comment:
**`--cors "*"` exposes the local MCP server to every web origin**

The generated supergateway command passes `--cors "*"`, which instructs supergateway to respond with `Access-Control-Allow-Origin: *`. While the server binds to `localhost`, CORS headers are what the *browser* enforces — any website the user visits while this proxy is running can send `fetch("http://localhost:8000/mcp", ...)` requests and read the responses. Local MCP servers routinely hold API keys in environment variables and may have filesystem or shell access, making this a meaningful cross-site exfiltration surface.

Consider narrowing the CORS origin to the gateway's own origin so only the Hadrian UI can reach the proxy. Since `buildInstallCommand` doesn't know the UI origin at call time, one approach is to derive the origin from `window.location.origin` and embed it:

```ts
const origin = window.location.origin;
command: `npx -y supergateway --cors "${origin}" --outputTransport streamableHttp --stdio ${quoted}`,
```

How can I resolve this? If you propose a fix, please make it concise.

@ScriptSmith ScriptSmith merged commit 5a51e59 into main Apr 21, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant