Skip to content

feat(ui): support serving under a subpath via MCP_REGISTRY_UI_BASE_PATH#1277

Open
nejch wants to merge 1 commit into
modelcontextprotocol:mainfrom
nejch:feat/ui-base-path
Open

feat(ui): support serving under a subpath via MCP_REGISTRY_UI_BASE_PATH#1277
nejch wants to merge 1 commit into
modelcontextprotocol:mainfrom
nejch:feat/ui-base-path

Conversation

@nejch
Copy link
Copy Markdown

@nejch nejch commented May 10, 2026

Motivation and Context

#757 added a simple UI, and although I understand this was a simple example and most production downstream registries will have a custom UI, I like the idea of starting small and staying consistent with https://registry.modelcontextprotocol.io/.

In my case this is at https://api.company.com/mcp/registry behind a reverse proxy, but the UI redirects and expects to be served at the root, so the UI is broken.

This PR adds subpath handling to the browser side template but does not adjust routing itself (this is normally handled by the reverse proxy).

If this is accepted I'd add a few more adjustments as a follow-up, so that downstream registries can use the same UI but a few custom options like the title and so on.

How Has This Been Tested?

docker compose + nginx

Dockerfile.dev

FROM golang:1.26-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /registry ./cmd/registry
FROM alpine:3
COPY --from=build /registry /registry
ENTRYPOINT ["/registry"]

nginx.conf

server {
    listen 9090;
    location /mcp/registry/ {
        proxy_pass http://registry:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    location = /mcp/registry {
        return 301 /mcp/registry/;
    }
}

docker-compose.dev.yml

services:
  registry:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: registry
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - MCP_REGISTRY_DATABASE_URL=postgres://mcpregistry:mcpregistry@postgres:5432/mcp-registry
      - MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION=false
      - MCP_REGISTRY_SEED_FROM=data/seed.json
      - MCP_REGISTRY_JWT_PRIVATE_KEY=8103179d8ef955f6d3de6d6217224a909ec4060529dfeb1d4ca5a994537658cd
      - MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH=true
      - MCP_REGISTRY_UI_BASE_PATH=/mcp/registry
    volumes:
      - ./data:/data:ro
    restart: "unless-stopped"
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    environment:
      POSTGRES_DB: mcp-registry
      POSTGRES_USER: mcpregistry
      POSTGRES_PASSWORD: mcpregistry
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mcpregistry -d mcp-registry"]
      interval: 1s
      retries: 30
    restart: "unless-stopped"
  nginx:
    image: nginx:alpine
    container_name: nginx
    depends_on:
      - registry
    ports:
      - "9090:9090"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    restart: "unless-stopped"
docker compose -f docker-compose.dev.yml up --build
  • Browse to http://localhost:9090/mcp/registry
  • API Reference link points to /mcp/registry/docs
  • Servers load correctly (API calls go through /mcp/registry/v0.1/servers)
  • Browser navigation/history stays under /mcp/registry

Also tested with /my-registry as the base path and without a path to confirm it works with arbitrary prefixes.

Breaking Changes

Shouldn't be breaking anything, but see testing example above.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@nejch nejch marked this pull request as ready for review May 10, 2026 19:21
// GetUIHTML returns the embedded HTML for the UI with the given base path
// substituted into browser-side links and navigation.
func GetUIHTML(basePath string) string {
return strings.ReplaceAll(embedUI, "{{UI_BASE_PATH}}", basePath)
Copy link
Copy Markdown
Author

@nejch nejch May 10, 2026

Choose a reason for hiding this comment

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

If this PR is accepted I could change the custom templating here in a follow-up to html/template or so to also accept things like custom titles and so on.

This was just a quick and minimal change.

Copy link
Copy Markdown

@GGOemea GGOemea left a comment

Choose a reason for hiding this comment

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

ok

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.

2 participants