Skip to content

sync#1

Open
Xtothepowerofinfinity wants to merge 296 commits into
XInfty:mainfrom
samanhappy:main
Open

sync#1
Xtothepowerofinfinity wants to merge 296 commits into
XInfty:mainfrom
samanhappy:main

Conversation

@Xtothepowerofinfinity
Copy link
Copy Markdown
Collaborator

@Xtothepowerofinfinity Xtothepowerofinfinity commented Nov 23, 2025

PR Type

Enhancement, Bug fix, Tests


Description

  • OAuth 2.0 Integration: Comprehensive OAuth support for upstream MCP servers with dynamic client registration (RFC 7591), authorization code flow with PKCE, and token management

  • OAuth Authorization Server: Full OAuth 2.0 server implementation with /oauth/authorize, /oauth/token, /oauth/userinfo endpoints and RFC 8414 metadata discovery

  • Session Persistence: SSE connection keepalive mechanism (30-second pings) and transparent session rebuild on server restart with configurable enableSessionRebuild flag

  • Bearer Token Authentication: Enhanced OAuth bearer token validation with resolveOAuthUserFromToken and RFC 9728 compliant WWW-Authenticate headers

  • Environment Variable Expansion: Support for ${VAR} and $VAR formats in server configurations with recursive expansion in nested objects and arrays

  • Smart Routing Enhancement: Added $smart/{group} pattern support for group-specific tool searching with proper nested path handling

  • URL Parameter Decoding: Fixed handling of special characters and slashes in server/tool/prompt names across controllers

  • Service Registry Refactor: Converted to async function with ES modules import() for better compatibility

  • MCP Registry Integration: Proxy endpoints for official MCP registry with cursor-based pagination and server discovery

  • OAuth Client Management: REST API for CRUD operations on OAuth clients with secret management

  • Configuration Management: Support for nameSeparator, enableSessionRebuild, and oauthServer configuration updates

  • Frontend Enhancements: OAuth server configuration UI, registry data hook with pagination, and URL encoding for tool/server names

  • Comprehensive Test Coverage: Tests for OAuth flows, session rebuild, keepalive pings, environment variable expansion, and smart routing


Diagram Walkthrough

flowchart LR
  A["Upstream MCP Servers"] -->|"OAuth Flow"| B["OAuth Client Registration"]
  B -->|"Dynamic Registration"| C["OAuth Settings Store"]
  C -->|"Token Management"| D["OAuth Service"]
  D -->|"Bearer Tokens"| E["SSE Service"]
  E -->|"Keepalive Pings"| F["Session Persistence"]
  G["Authorization Server"] -->|"Authorize/Token"| H["OAuth Server Service"]
  H -->|"Client Lookup"| I["OAuth Model"]
  I -->|"Persistence"| C
  J["MCP Registry"] -->|"Server Discovery"| K["Registry Controller"]
  K -->|"Pagination"| L["Frontend Registry Hook"]
  M["Smart Routing"] -->|"Group Filtering"| N["MCP Service"]
  N -->|"Tool Search"| O["Vector Search"]
Loading

File Walkthrough

Relevant files
Enhancement
26 files
mcpService.ts
OAuth integration and transport async handling for MCP servers

src/services/mcpService.ts

  • Added OAuth support with dynamic client registration and token
    management for upstream MCP servers
  • Implemented environment variable expansion in server configurations
    and tool/prompt name separators
  • Enhanced transport creation to support async OAuth provider
    initialization
  • Added session reconnection logic and improved error handling for SSE
    and HTTP transports
  • Refactored server initialization to use expanded configurations and
    handle OAuth authorization states
  • Added support for $smart/{group} pattern for group-specific tool
    searching
+436/-265
sseService.ts
Session persistence and OAuth bearer token support for SSE

src/services/sseService.ts

  • Enhanced bearer token validation to support OAuth tokens via
    resolveOAuthUserFromToken
  • Added keepalive ping mechanism (every 30 seconds) to prevent SSE
    connection timeouts
  • Implemented session rebuild functionality with transparent recovery on
    server restart
  • Added user context attachment from OAuth bearer tokens
  • Improved error handling with RFC 9728 compliant WWW-Authenticate
    headers
  • Added session creation locks to prevent concurrent session
    initialization conflicts
+546/-47
oauthServerController.ts
OAuth 2.0 server endpoints and authorization flow implementation

src/controllers/oauthServerController.ts

  • New controller implementing OAuth 2.0 authorization endpoints
    (/oauth/authorize, /oauth/token, /oauth/userinfo)
  • Added RFC 8414 authorization server metadata endpoint and RFC 9728
    protected resource metadata endpoint
  • Implemented authorization consent page with i18n support and HTML
    escaping for security
  • Added JWT token resolution for authentication in OAuth flows
  • Supports both dynamic client registration and static client
    configuration
+525/-0 
oauthClientRegistration.ts
Dynamic OAuth client registration and token management service

src/services/oauthClientRegistration.ts

  • Implements RFC 7591 dynamic client registration for upstream MCP
    servers
  • Added auto-detection of OAuth configuration from 401 responses with
    WWW-Authenticate headers
  • Supports RFC 9728 protected resource metadata discovery
  • Implements authorization code flow with PKCE support
  • Provides token refresh and client credential management
+584/-0 
mcpOAuthProvider.ts
MCP SDK OAuth provider implementation for upstream servers

src/services/mcpOAuthProvider.ts

  • Implements OAuthClientProvider interface from MCP SDK for server-side
    OAuth flows
  • Handles client information persistence, token storage, and PKCE code
    verifier management
  • Integrates with OAuth settings store for configuration persistence
  • Manages authorization URL generation and redirect handling for web UI
  • Supports credential invalidation and scope auto-detection from servers
+593/-0 
registry.ts
Service registry async refactor for ES modules support     

src/services/registry.ts

  • Converted service registration to async function to support dynamic
    module loading
  • Replaced CommonJS createRequire with ES modules import() for better
    compatibility
  • Added tryLoadOverride helper function with improved error handling
  • Maintains backward compatibility with service override loading
    mechanism
+23/-15 
oauthDynamicRegistrationController.ts
OAuth Dynamic Client Registration Controller Implementation

src/controllers/oauthDynamicRegistrationController.ts

  • Implements RFC 7591 Dynamic Client Registration endpoints for OAuth
    clients
  • Provides POST /oauth/register for client registration with validation
    of redirect URIs, grant types, and scopes
  • Implements GET/PUT/DELETE endpoints for client configuration
    management with registration access token authentication
  • Includes token generation and verification with 30-day expiration for
    registration access tokens
+543/-0 
oauthCallbackController.ts
OAuth Callback Handler for MCP Server Authorization           

src/controllers/oauthCallbackController.ts

  • Handles OAuth 2.0 authorization callbacks from upstream MCP servers
  • Extracts authorization code and state parameter, validates server
    identity
  • Calls transport.finishAuth() to exchange authorization code for tokens
  • Reconnects client with refreshed transport and updates server status
    to connected
  • Generates HTML response pages with i18n support for success/error
    scenarios
+388/-0 
oauthServerService.ts
OAuth 2.0 Authorization Server Service Implementation       

src/services/oauthServerService.ts

  • Implements OAuth2Server model with authorization code and refresh
    token flows
  • Provides client lookup, token management, and scope validation
    functions
  • Initializes OAuth2 server with configurable token lifetimes and PKCE
    support
  • Exports handlers for authorize, token, and authenticate requests
+435/-0 
OAuth.ts
OAuth Data Model and Persistence Layer                                     

src/models/OAuth.ts

  • Implements OAuth client, authorization code, and token storage with
    in-memory and persistent layers
  • Provides CRUD operations for OAuth clients stored in settings
  • Manages authorization codes with expiration validation
  • Handles token persistence to mcp_settings.json with automatic cleanup
    of expired tokens
  • Includes periodic cleanup interval (5 minutes) for expired codes and
    tokens
+347/-0 
serverController.ts
Server Controller OAuth and Configuration Updates               

src/controllers/serverController.ts

  • Adds URL decoding for serverName, toolName, and promptName parameters
    to handle slashes
  • Refactors updateSystemConfig validation logic with individual flag
    checks for each config section
  • Adds support for nameSeparator, enableSessionRebuild, and oauthServer
    configuration updates
  • Initializes default OAuth server configuration with dynamic
    registration settings
+153/-26
index.ts
OAuth Type Definitions and Server Configuration Interfaces

src/types/index.ts

  • Adds OAuthProviderConfig interface for upstream MCP server OAuth
    configuration
  • Defines IOAuthClient, IOAuthAuthorizationCode, IOAuthToken interfaces
    for authorization server
  • Adds OAuthServerConfig with token lifetimes, PKCE support, and dynamic
    registration settings
  • Extends ServerConfig with comprehensive OAuth fields including
    static/dynamic registration and PKCE
  • Adds oauth field to ServerInfo for tracking authorization state and
    URLs
+149/-7 
useRegistryData.ts
Registry Data Hook with Cursor-Based Pagination                   

frontend/src/hooks/useRegistryData.ts

  • Implements cursor-based pagination for registry server listing with
    nextCursor tracking
  • Provides functions to fetch servers by name, versions, and specific
    versions with URL encoding
  • Includes search functionality with cursor history for back navigation
  • Manages pagination state including currentPage, serversPerPage, and
    hasNextPage
+283/-0 
useSettingsData.ts
OAuth server configuration and system settings management

frontend/src/hooks/useSettingsData.ts

  • Added OAuthServerConfig interface with OAuth server configuration
    properties including token lifetimes, scopes, and dynamic registration
    settings
  • Added state management for oauthServerConfig, nameSeparator, and
    enableSessionRebuild with default values
  • Implemented data loading logic to populate OAuth and system config
    settings from API responses
  • Added three new update functions: updateOAuthServerConfig,
    updateOAuthServerConfigBatch, updateNameSeparator,
    updateSessionRebuild, and exportMCPSettings
  • Exported new state variables and update functions in the hook's return
    object
+238/-0 
oauthService.ts
OAuth provider initialization and token management service

src/services/oauthService.ts

  • Created new OAuth service module for managing OAuth provider
    initialization and token handling
  • Implemented initOAuthProvider() to create and configure
    ProxyOAuthServerProvider from system settings
  • Added functions to retrieve OAuth tokens for servers, handle token
    refresh, and add OAuth headers to requests
  • Implemented initializeAllOAuthClients() to pre-register OAuth clients
    for configured servers
  • Re-exported OAuth client registration utilities from
    oauthClientRegistration.js
+271/-0 
path.ts
Enhanced package root discovery and configuration file path resolution

src/utils/path.ts

  • Added findPackageRoot() function to locate package root directory with
    caching and multiple fallback strategies
  • Implemented initializePackageRoot() to cache package root on module
    load for performance
  • Enhanced getConfigFilePath() to prioritize environment variable paths
    and check package root directory
  • Added support for finding package root from module directory using
    getCurrentModuleDir()
  • Improved path resolution for both development and installed package
    scenarios
+206/-2 
oauthClientController.ts
OAuth client management REST API controller                           

src/controllers/oauthClientController.ts

  • Implemented REST API endpoints for OAuth client management (CRUD
    operations)
  • Added getAllClients() to list clients with sanitized output (no
    secrets exposed)
  • Implemented getClient(), createClient(), updateClient(), and
    deleteClient() endpoints
  • Added regenerateSecret() endpoint to generate new client secrets
  • Includes validation, error handling, and security measures to prevent
    secret exposure
+276/-0 
server.ts
OAuth integration and smart routing path handling               

src/server.ts

  • Added OAuth provider initialization at server startup with router
    mounting
  • Implemented getCurrentFileDir() function to handle module directory
    resolution in different environments
  • Updated route patterns from :group? to :group(.*)? to properly handle
    nested group paths with slashes
  • Refactored findPackageRoot() to use shared utility function from
    path.ts
  • Added OAuth server initialization call during server setup
+47/-56 
index.ts
OAuth configuration and MCP registry type definitions       

frontend/src/types/index.ts

  • Added oauth_required status to ServerStatus type for OAuth
    authorization flow
  • Extended ServerConfig with comprehensive OAuth configuration
    properties including dynamic registration
  • Added oauth field to Server interface with authorization URL and state
  • Added OAuth fields to ServerFormData interface for form handling
  • Added Registry types for official MCP registry integration (packages,
    remotes, icons, metadata)
  • Extended AuthResponse with isUsingDefaultPassword flag
+198/-1 
registryController.ts
MCP registry proxy controller endpoints                                   

src/controllers/registryController.ts

  • Implemented three endpoints to proxy requests to official MCP registry
  • Added getAllRegistryServers() with cursor-based pagination and search
    support
  • Implemented getRegistryServerVersions() to fetch all versions of a
    specific server
  • Added getRegistryServerVersion() to fetch a specific server version
  • Includes proper error handling and URL encoding for server names and
    versions
+169/-0 
index.ts
OAuth and registry endpoints routing configuration             

src/routes/index.ts

  • Added OAuth callback endpoint at /oauth/callback for handling OAuth
    authorization responses
  • Implemented OAuth Authorization Server endpoints (authorize, token,
    userinfo, metadata)
  • Added RFC 7591 Dynamic Client Registration endpoints for public client
    registration
  • Added OAuth client management routes (CRUD operations) with validation
  • Integrated registry proxy routes for MCP server discovery
  • Added MCP settings export route and imported new controllers
+73/-1   
oauthSettingsStore.ts
OAuth settings persistence and mutation service                   

src/services/oauthSettingsStore.ts

  • Created service for managing OAuth settings persistence in server
    configuration
  • Implemented loadServerConfig() to retrieve server OAuth configuration
  • Added mutateOAuthSettings() for atomic updates to OAuth settings with
    persistence
  • Implemented helper functions: persistClientCredentials(),
    persistTokens(), updatePendingAuthorization(), clearOAuthData()
  • Provides type-safe interface for OAuth configuration mutations
+158/-0 
openApiController.ts
OpenAPI controller refactoring with URL decoding support 

src/controllers/openApiController.ts

  • Extracted parameter type conversion logic to separate utility module
    parameterConversion.js
  • Updated executeToolViaOpenAPI() to decode URL-encoded server and tool
    names
  • Implemented proper tool name matching using getNameSeparator() for
    flexible naming
  • Improved tool lookup to use matched tool's actual name for subsequent
    calls
  • Simplified controller by delegating conversion to utility function
+12/-75 
index.ts
Settings loading and environment variable expansion enhancements

src/config/index.ts

  • Added ensureOAuthServerDefaults() to initialize default OAuth server
    configuration
  • Enhanced loadOriginalSettings() to handle missing settings file
    gracefully with defaults
  • Improved error handling to throw errors instead of silently returning
    defaults
  • Enhanced replaceEnvVars() to recursively handle nested objects and
    arrays
  • Added getNameSeparator() function to retrieve configured name
    separator from settings
  • Imported OAuth server defaults from constants module
+58/-12 
dataService.ts
Data service user-aware filtering and permissions               

src/services/dataService.ts

  • Refactored DataService from interface-based to class-based
    implementation
  • Implemented user-aware filtering in filterData() and filterSettings()
    methods
  • Added admin user detection to return unfiltered data for
    administrators
  • Enhanced mergeSettings() to handle user-specific configurations and
    OAuth-related settings
  • Improved getPermissions() to return appropriate permissions based on
    admin status
  • Integrated UserContextService for current user context
+59/-21 
toolService.ts
Tool service URL encoding for server and tool names           

frontend/src/services/toolService.ts

  • Added URL encoding for server and tool names in callTool() function to
    handle slashes
  • Updated toggleTool() to URL-encode server and tool names in API
    endpoint
  • Enhanced updateToolDescription() to URL-encode server and tool names
  • Ensures proper handling of server names with slashes (e.g.,
    com.atlassian/atlassian-mcp-server)
+8/-3     
Bug fix
1 files
promptController.ts
URL parameter decoding for prompt controller                         

src/controllers/promptController.ts

  • Added URL decoding for serverName and promptName parameters to handle
    special characters and slashes
  • Ensures proper handling of server/prompt names with separators in URL
    paths
+3/-1     
Tests
12 files
sseService.test.ts
SSE Service Tests with Bearer Auth and Session Rebuild     

src/services/sseService.test.ts

  • Adds comprehensive test coverage for bearer token authentication with
    RFC 8707 resource metadata
  • Implements expectBearerUnauthorized helper for consistent 401 response
    validation
  • Tests session rebuild functionality with enableSessionRebuild
    configuration flag
  • Adds tests for transparent session recovery when enabled vs. error
    responses when disabled
+171/-26
replaceEnvVars.test.ts
Environment Variable Expansion Configuration Tests             

tests/config/replaceEnvVars.test.ts

  • Comprehensive test suite for environment variable expansion in
    configuration
  • Tests expandEnvVars for ${VAR} and $VAR formats with multiple
    variables
  • Tests replaceEnvVars for recursive expansion in nested objects,
    arrays, and mixed structures
  • Includes real-world scenarios for OAuth, OpenAPI, and server
    configurations
+343/-0 
keepalive.test.ts
Keepalive Ping Functionality Tests for SSE Connections     

tests/services/keepalive.test.ts

  • Tests keepalive ping functionality for SSE connections at 30-second
    intervals
  • Verifies interval creation, cleanup on connection close, and error
    handling
  • Tests that pings stop after connection closure and multiple pings are
    sent correctly
  • Mocks timer functions to track interval lifecycle and validate
    keepalive behavior
+303/-0 
mcpService-smart-routing-group.test.ts
MCP Service Smart Routing with Group Support Tests             

tests/services/mcpService-smart-routing-group.test.ts

  • Tests smart routing with group filtering for $smart and $smart/{group}
    patterns
  • Validates search_tools and call_tool functionality with server
    filtering
  • Tests empty group handling and query parameter validation
  • Verifies vector search is called with correct server filters based on
    group membership
+222/-0 
parameterConversion.test.ts
Parameter type conversion utility test suite                         

tests/utils/parameterConversion.test.ts

  • Created comprehensive test suite for convertParametersToTypes()
    utility function
  • Tests cover type conversions for number, integer, boolean, array, and
    object types
  • Includes edge cases like invalid conversions, empty strings, and mixed
    parameter types
  • Validates proper handling of already-typed values and missing schema
    definitions
+259/-0 
oauth.test.ts
OAuth model operations test suite                                               

tests/models/oauth.test.ts

  • Created test suite for OAuth model functions covering client,
    authorization code, and token management
  • Tests verify CRUD operations for OAuth clients with duplicate
    prevention
  • Includes tests for authorization code lifecycle (save, retrieve,
    revoke, expiration)
  • Validates token management including access tokens, refresh tokens,
    and expiration handling
+236/-0 
mcpService-headers.test.ts
Environment variable expansion in headers test suite         

tests/services/mcpService-headers.test.ts

  • Created test suite for environment variable expansion in headers and
    arguments
  • Tests cover expandEnvVars() and replaceEnvVars() functions with
    various formats (${VAR} and $VAR)
  • Includes real-world Context7 API key scenario testing
  • Validates handling of undefined variables, nested objects, and arrays
+179/-0 
oauthService.test.ts
OAuth service initialization and token handling tests       

tests/services/oauthService.test.ts

  • Created test suite for OAuth service initialization and token
    management
  • Tests verify OAuth provider initialization behavior when
    enabled/disabled
  • Validates server OAuth token retrieval and Authorization header
    addition
  • Includes tests for handling missing OAuth configuration and token
    scenarios
+211/-0 
cliPathHandling.test.ts
CLI path handling and ESM URL conversion tests                     

tests/utils/cliPathHandling.test.ts

  • Created test suite for cross-platform ESM URL conversion using
    pathToFileURL()
  • Tests verify proper handling of Unix and Windows paths, relative
    paths, and special characters
  • Validates URL encoding of spaces and special characters in paths
  • Includes tests for path normalization and CLI use case scenarios
+131/-0 
openApiController.test.ts
OpenAPI controller tests refactoring and URL encoding validation

tests/controllers/openApiController.test.ts

  • Refactored tests to use extracted convertParametersToTypes() utility
    function
  • Updated test imports to reference the new utility module
  • Added test for URL-encoded server and tool names with slashes (e.g.,
    com.atlassian/atlassian-mcp-server)
  • Maintained existing parameter type conversion test coverage
+22/-76 
configController.test.ts
Configuration controller export functionality tests           

tests/controllers/configController.test.ts

  • Created test suite for getMcpSettingsJson() controller function
  • Tests cover full settings export and individual server export
    scenarios
  • Validates proper handling of missing users array and non-existent
    servers
  • Includes error handling tests for graceful failure responses
+139/-0 
server-smart-routing.test.ts
Smart routing integration tests for nested paths                 

tests/integration/server-smart-routing.test.ts

  • Created integration test suite for smart routing with nested group
    path segments
  • Tests verify proper routing of global and user-scoped MCP requests
    with complex paths
  • Validates that nested segments like $smart/test-group are correctly
    captured in route parameters
  • Includes mocking of services and middleware for isolated testing
+98/-0   
Additional files
101 files
DOCKER_CLI_TEST.md +124/-0 
copilot-instructions.md +30/-4   
.prettierrc +1/-1     
AGENTS.md +30/-0   
Dockerfile +10/-0   
README.md +105/-0 
README.zh.md +65/-0   
SECURITY_SUMMARY.md +187/-0 
cli.js +5/-3     
openapi.mdx +26/-0   
prompts.mdx +142/-0 
servers.mdx +56/-0   
system.mdx +113/-0 
tools.mdx +86/-0   
users.mdx +195/-0 
docker-setup.mdx +44/-0   
contributing.mdx +1/-1     
docs.json +15/-5   
environment-variables.md +267/-0 
oauth.mdx +141/-0 
smart-routing.mdx +75/-0   
installation.mdx +39/-14 
oauth-dynamic-registration-implementation.md +169/-0 
oauth-server.md +538/-0 
openapi.mdx +26/-0   
prompts.mdx +142/-0 
servers.mdx +56/-0   
system.mdx +113/-0 
tools.mdx +86/-0   
users.mdx +195/-0 
docker-setup.mdx +44/-0   
development.mdx +1/-1     
code.mdx +4/-4     
navigation.mdx +1/-2     
oauth.mdx +141/-0 
index.mdx +1/-13   
entrypoint.sh +30/-1   
mcp_settings_with_env_vars.json +80/-0   
oauth-dynamic-registration-config.json +25/-0   
oauth-server-config.json +76/-0   
AddUserForm.tsx +178/-0 
ChangePasswordForm.tsx +34/-2   
EditServerForm.tsx +29/-28 
EditUserForm.tsx +200/-0 
JSONImportForm.tsx +311/-0 
MarketServerDetail.tsx +55/-41 
RegistryServerCard.tsx +205/-0 
RegistryServerDetail.tsx +698/-0 
ServerCard.tsx +209/-116
ServerForm.tsx +613/-256
ServerToolConfig.tsx +4/-2     
UserCard.tsx +96/-0   
Badge.tsx +23/-13 
CursorPagination.tsx +78/-0   
DefaultPasswordWarningModal.tsx +116/-0 
LanguageSwitch.tsx +2/-1     
PromptCard.tsx +4/-2     
ToolCard.tsx +55/-4   
permissions.ts +4/-1     
AuthContext.tsx +9/-6     
ServerContext.tsx +136/-108
useCloudData.ts +7/-3     
useUserData.ts +100/-0 
i18n.ts +4/-0     
index.css +12/-0   
Dashboard.tsx +134/-33
LoginPage.tsx +86/-6   
MarketPage.tsx +340/-85
ServersPage.tsx +24/-0   
SettingsPage.tsx +736/-100
UsersPage.tsx +220/-2 
configService.ts +3/-0     
promptService.ts +11/-5   
passwordValidation.ts +38/-0   
jest.config.cjs +1/-1     
en.json +174/-7 
fr.json +174/-7 
tr.json +790/-0 
zh.json +174/-7 
mcp_settings.json +23/-1   
package.json +11/-9   
pnpm-lock.yaml +1715/-1010
oauthServerDefaults.ts +42/-0   
authController.ts +19/-0   
cloudController.ts +5/-2     
configController.ts +44/-1   
toolController.ts +23/-3   
userController.ts +25/-1   
examples.ts +1/-1     
auth.ts +26/-2   
userContext.ts +27/-14 
dataService.test.ts +0/-13   
marketService.ts +5/-0     
openApiGeneratorService.ts +14/-4   
services.ts +2/-7     
moduleDir.ts +11/-0   
oauthBearer.ts +42/-0   
parameterConversion.ts +93/-0   
passwordValidation.ts +49/-0   
version.ts +13/-2   
Additional files not shown

Copilot AI and others added 10 commits November 19, 2025 11:41
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
…413)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
Co-authored-by: samanhappy <samanhappy@gmail.com>
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

We failed to fetch the diff for pull request #1

You can try again by commenting this pull request with @sourcery-ai review, or contact us for help.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 23, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds DB-backed DAO layer and DB-mode boot path, a full OAuth2 authorization server (incl. RFC 7591 dynamic client registration and callback handling), a registry proxy/marketplace, many backend controllers/routes, broad frontend UIs (settings, users, activity, imports, registry), Docker/entrypoint adjustments, localization updates, and supporting docs/examples.

Changes

Cohort / File(s) Summary
OAuth server & clients
src/controllers/oauthServerController.*, src/controllers/oauthClientController.*, src/controllers/oauthCallbackController.*, src/controllers/oauthDynamicRegistrationController.*, src/models/OAuth.ts, src/constants/oauthServerDefaults.ts
Implements authorization endpoints (authorize/token/userinfo/metadata), OAuth callback handling, client CRUD/regenerate-secret, RFC7591 dynamic registration, in-memory tokens/authorization codes with persistence and defaults. Review consent UI, token lifecycle, and secret handling.
DAO / DB-mode & factory
src/dao/..., src/dao/DaoFactory.ts, src/dao/DatabaseDaoFactory.ts, src/config/DaoConfigService.ts, src/index.ts, src/dao/DatabaseDaoFactory.ts
Adds DB-mode detection/initialization (initializeDaoFactory), factory switching between file and DB DAOs, and boot-path DB initialization. Check environment switching, startup ordering, and error paths.
DB entities & repositories
src/db/entities/*, src/db/repositories/*, src/db/repositories/index.ts
New TypeORM entities (User, Server, Group, SystemConfig, UserConfig) and repositories with CRUD/query APIs and singleton SystemConfig handling. Verify DB mappings, column types, JSON fields, indexes and defaults.
DB-backed DAO implementations
src/dao/*DbImpl.ts
Database-backed DAO implementations (UserDaoDbImpl, ServerDaoDbImpl, GroupDaoDbImpl, SystemConfigDaoDbImpl, UserConfigDaoDbImpl) mapped to repositories. Inspect transactional behaviour, mapping correctness and error handling.
Controllers, registry & routes
src/controllers/*, src/controllers/registryController.ts, src/controllers/configController.ts, src/routes/index.ts
Adds MCP settings export, registry proxy endpoints (servers/versions/version), OAuth callback, dynamic registration, OAuth client admin routes and many controller integrations. Review route auth, parameter encoding/decoding, and consistent error responses.
Auth middleware & user context
src/middlewares/auth.ts, src/middlewares/userContext.ts
Auth middleware made async, adds OAuth bearer resolution and context attachment, SSE user cleanup, and ContextAwareDataService abstraction. Validate middleware async usage and request lifecycle effects.
Startup, config & utils
src/config/index.ts, src/constants/oauthServerDefaults.ts, src/server.ts
Adds ensureOAuthServerDefaults, getNameSeparator export, improved recursive env var expansion, package-root utilities, and integrates OAuth/DB init at startup. Check persistence of defaults and startup ordering.
Tool/prompt/OpenAPI handling
src/controllers/toolController.ts, src/controllers/openApiController.ts, src/controllers/promptController.ts, src/utils/parameterConversion.*
Adds URL encode/decode for path segments, argument type conversion from input schemas, and nameSeparator-aware tool naming. Verify encoding consistency and parameter conversion correctness.
Registry (backend + frontend)
src/controllers/registryController.ts, frontend/src/hooks/useRegistryData.ts, frontend/src/pages/MarketPage.tsx, frontend/src/components/RegistryServer*
Backend registry proxy plus frontend browsing/install flows (cursor pagination, search, versions). Audit proxy error handling, pagination cursors, and install integration paths.
Frontend — Settings & contexts
frontend/src/contexts/SettingsContext.tsx, frontend/src/hooks/useSettingsData.ts, frontend/src/services/configService.ts
Adds SettingsProvider and useSettings, Bearer key CRUD, OAuth server UI and nameSeparator support; useSettingsData now delegates to SettingsContext. Review state sync, optimistic updates and permission checks.
Frontend — Users, activity, imports
frontend/src/hooks/useUserData.ts, frontend/src/pages/UsersPage.tsx, frontend/src/pages/ActivityPage.tsx, frontend/src/components/JSONImportForm.tsx, frontend/src/components/GroupImportForm.tsx, frontend/src/components/McpbUploadForm.tsx
New user-management hook/UI, activity analytics page, JSON/group import forms, MCPB upload & install UI. Inspect validation, batch import behavior, and error reporting.
Frontend — components & UI updates
frontend/src/components/*, frontend/src/components/ui/*, frontend/src/pages/*
Many new/updated components (Add/Edit User, RegistryServerCard/Detail, UserCard, MultiSelect, CursorPagination, DefaultPasswordWarningModal, etc.), status oauth_required support, copy/reload/OAuth actions, and i18n key usage. Verify prop typings and translation keys.
Frontend — auth/login flow
frontend/src/contexts/AuthContext.tsx, frontend/src/pages/LoginPage.tsx, frontend/src/services/authService.ts
login() now returns structured result {success,isUsingDefaultPassword?,message?}; LoginPage adds default-password modal, social login (Better Auth), sanitized returnUrl and redirect token handling. Ensure consumers updated to new return shape.
Types & validation
frontend/src/types/index.ts, frontend/src/utils/passwordValidation.ts
Extends types (oauth, keepAlive, registry, BearerKey), adds password strength validator and AuthResponse.isUsingDefaultPassword. Check cross-file type consistency.
Server context & pagination
frontend/src/contexts/ServerContext.tsx, frontend/src/hooks/useRegistryData.ts
Adds pagination (currentPage, serversPerPage), allServers vs paginated sets, page controls, and registry cursor pagination hook. Verify effect deps and UI sync.
User management & security (backend)
src/controllers/userController.ts, src/controllers/authController.ts
User endpoints converted to async DAO-backed flows, server-side password strength checks, and login response exposing isUsingDefaultPassword. Review validation, error codes, and admin checks.
Docker & entrypoint updates
Dockerfile, entrypoint.sh, docker-compose.db.yml, .github/DOCKER_CLI_TEST.md, docs/configuration/docker-setup.mdx
Optional Docker Engine install under INSTALL_EXT, entrypoint can auto-start dockerd, new docker-compose.db.yml (pgvector + samanhappy/mcphub), and docs/tests. Audit privileged dockerd auto-start and security notes.
Docs, examples & locales
docs/**, locales/*, examples/*, mcp_settings.json, AGENTS.md
Extensive docs (OAuth, DB mode, API refs, smart-routing, prompts/tools/users), Turkish locale added, example OAuth configs, and mcp_settings.json updated with oauthServer. Validate sample values and links.
Package, CI & tooling
package.json, jest.config.cjs, .github/workflows/ci.yml, .gitignore, .prettierrc, eslint.config.mjs
Dependency bumps/additions (openid-client, oauth2 server, openai, etc.), jest ignore updates, CI env rename to DB_URL, .gitignore additions, newline fix, and new ESLint flat config. Check dependency compatibility and CI env consistency.
Removed docs & cleanup
docs/dao-layer.md, docs/dao-implementation-summary.md, docs/openapi-support.md, docs/openapi-schema-support.md, docs/testing-framework.md, .github/copilot-instructions.md, googled*.html
Removes several legacy docs and Google verification file. Confirm no remaining references to deleted docs.
Misc / CLI / utilities
bin/cli.js, src/config/index.ts, src/middlewares/*, src/controllers/*
Minor CLI change (pathToFileURL), improved env var expansion, getNameSeparator export, async middleware signatures, and controller refinements. Check backward compatibility for exported helpers.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant App as MCPHub (App)
    participant OAuth as OAuth Server
    participant DB as Postgres DB

    Browser->>App: GET /oauth/authorize (start auth)
    App->>OAuth: Render consent / issue PKCE challenge
    Browser->>OAuth: User approves (consent), OAuth returns code+state
    OAuth->>Browser: Redirect to /oauth/callback?code&state
    Browser->>App: GET /oauth/callback?code&state
    App->>OAuth: Exchange code for tokens (finishAuth)
    OAuth->>App: Return tokens
    App->>DB: Persist tokens/clients (saveToken/createClient) (rgba(34,139,34,0.5))
    App->>App: Rebuild transports / refresh server info (rgba(70,130,180,0.5))
    App->>Browser: Return success page or redirect
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I nibbled through configs, tokens, and docs,
I tunneled DAO paths and patched tiny socks,
I hopped through consent and registry lanes,
I carried new locales and tidying pains,
A carrot-sized PR — hooray for new gains!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Nov 23, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Weak token management

Description: Registration access tokens for dynamic client registration are stored in an in-memory Map
without persistence or binding to a specific client secret/subject, making them long-lived
(30 days), replayable, and not revocable across processes; an attacker with server read
access or after a restart could bypass authentication on the client configuration
endpoints.
oauthDynamicRegistrationController.ts [12-45]

Referred Code
// Store registration access tokens (in production, use database)
const registrationTokens = new Map<string, { clientId: string; createdAt: Date }>();

/**
 * Generate registration access token
 */
const generateRegistrationToken = (clientId: string): string => {
  const token = crypto.randomBytes(32).toString('hex');
  registrationTokens.set(token, {
    clientId,
    createdAt: new Date(),
  });
  return token;
};

/**
 * Verify registration access token
 */
const verifyRegistrationToken = (token: string): string | null => {
  const data = registrationTokens.get(token);
  if (!data) {



 ... (clipped 13 lines)
Insecure auth token use

Description: The authorization endpoints accept a JWT from header/query/body ('x-auth-token' and
'token') and verify it with a shared secret, enabling bearer token leakage via URLs and
logs and allowing fixation/replay if tokens are long-lived; query/body tokens should not
be accepted and CSRF protections are not visible.
oauthServerController.ts [20-46]

Referred Code
/**
 * Attempt to attach a user to the request based on a JWT token present in header, query, or body.
 */
function resolveUserFromRequest(req: Request): AuthenticatedUser | null {
  const headerToken = req.header('x-auth-token');
  const queryToken = typeof req.query.token === 'string' ? req.query.token : undefined;
  const bodyToken =
    req.body && typeof (req.body as Record<string, unknown>).token === 'string'
      ? ((req.body as Record<string, string>).token as string)
      : undefined;
  const token = headerToken || queryToken || bodyToken;

  if (!token) {
    return null;
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as { user?: AuthenticatedUser };
    if (decoded?.user) {
      return decoded.user;
    }



 ... (clipped 6 lines)
Redirect URI validation

Description: The authorize endpoint validates 'redirect_uri' only by checking it is in the client's
list but does not enforce exact match normalization (e.g., Unicode/punycode casing,
trailing slashes) or prevent open redirect nuances; ensure strict exact match against
registered URIs after normalization.
oauthServerController.ts [173-225]

Referred Code
/**
 * GET /oauth/authorize
 * Display authorization page or handle authorization
 */
export const getAuthorize = async (req: Request, res: Response): Promise<void> => {
  try {
    const oauth = getOAuthServer();
    if (!oauth) {
      res.status(503).json({ error: 'OAuth server not available' });
      return;
    }

    // Get and validate query parameters
    const client_id = validateQueryParam(req.query.client_id, 'client_id', /^[a-zA-Z0-9_-]+$/);
    const redirect_uri = validateQueryParam(req.query.redirect_uri, 'redirect_uri');
    const response_type = validateQueryParam(req.query.response_type, 'response_type', /^code$/);
    const scope = req.query.scope
      ? validateQueryParam(req.query.scope, 'scope', /^[a-zA-Z0-9_ ]+$/)
      : undefined;
    const state = req.query.state
      ? validateQueryParam(req.query.state, 'state', /^[a-zA-Z0-9_-]+$/)



 ... (clipped 32 lines)
OAuth grant misuse

Description: Dynamic client registration allows 'client_secret' omittance when
'token_endpoint_auth_method' is 'none', potentially issuing public clients that can
request refresh tokens; ensure grant-types-to-auth-method constraints (e.g., public
clients must not receive refresh_token grant in server-side flows).
oauthDynamicRegistrationController.ts [118-141]

Referred Code
// Determine if client secret is needed based on token_endpoint_auth_method
const authMethod = token_endpoint_auth_method || 'client_secret_basic';
const needsSecret = authMethod !== 'none';
const clientSecret = needsSecret ? crypto.randomBytes(32).toString('hex') : undefined;

// Default grant types
const defaultGrantTypes = ['authorization_code', 'refresh_token'];
const clientGrantTypes = grant_types || defaultGrantTypes;

// Validate grant types
const allowedGrantTypes = oauthConfig.dynamicRegistration.allowedGrantTypes || [
  'authorization_code',
  'refresh_token',
];
for (const grantType of clientGrantTypes) {
  if (!allowedGrantTypes.includes(grantType)) {
    res.status(400).json({
      error: 'invalid_client_metadata',
      error_description: `Grant type not allowed: ${grantType}`,
    });



 ... (clipped 3 lines)
Header injection risk

Description: The WWW-Authenticate header echoes a resource metadata URL derived from request headers
like 'x-forwarded-host' and ':authority' without stringent validation, which could enable
header injection into challenges if upstream proxy sanitization is weak; ensure strict
header parsing and allowed host list to avoid phishing vectors.
sseService.ts [130-166]

Referred Code
const sendBearerAuthError = (req: Request, res: Response, reason: 'missing' | 'invalid'): void => {
  const errorDescription =
    reason === 'missing' ? 'No authorization provided' : 'Invalid bearer token';

  const resourceMetadataUrl = buildResourceMetadataUrl(req);
  const headerParts = [
    'error="invalid_token"',
    `error_description="${escapeHeaderValue(errorDescription)}"`,
  ];

  if (resourceMetadataUrl) {
    headerParts.push(`resource_metadata="${escapeHeaderValue(resourceMetadataUrl)}"`);
  }

  console.warn(
    reason === 'missing'
      ? 'Bearer authentication required but no authorization header was provided'
      : 'Bearer authentication failed due to invalid bearer token',
  );

  res.setHeader('WWW-Authenticate', `Bearer ${headerParts.join(', ')}`);



 ... (clipped 16 lines)
DoS via keepalive logging

Description: Keepalive pings are sent on an interval without tracking maximum retries or disconnect
backoff, which can be abused to generate log/CPU load if an endpoint is held open by an
attacker; consider limiting retries and removing verbose logs in steady-state.
sseService.ts [217-231]

Referred Code
const keepAlive = setInterval(() => {
  try {
    // Send a ping notification to keep the connection alive
    transport.send({ jsonrpc: '2.0', method: 'ping' });
    console.log(`Sent keepalive ping for SSE session: ${transport.sessionId}`);
  } catch (e) {
    // If sending a ping fails, the connection is likely broken.
    // Log the error and clear the interval to prevent further attempts.
    console.warn(
      `Failed to send keepalive ping for SSE session ${transport.sessionId}, cleaning up interval:`,
      e,
    );
    clearInterval(keepAlive);
  }
}, 30000); // Send ping every 30 seconds
Session fixation risk

Description: Session rebuild mechanism accepts arbitrary session IDs supplied by clients and recreates
sessions when 'enableSessionRebuild' is true, which could allow session fixation or
hijacking across restarts; bind sessions to authenticated user identity and verify
ownership before rebuilding.
sseService.ts [299-377]

Referred Code
async function createSessionWithId(
  sessionId: string,
  group: string,
  username?: string,
): Promise<StreamableHTTPServerTransport> {
  console.log(
    `[SESSION REBUILD] Starting session rebuild for ID: ${sessionId}${username ? ` for user: ${username}` : ''}`,
  );

  // Create a new server instance to ensure clean state
  const server = getMcpServer(sessionId, group);

  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => sessionId, // Use the specified sessionId
    onsessioninitialized: (initializedSessionId) => {
      console.log(
        `[SESSION REBUILD] onsessioninitialized triggered for ID: ${initializedSessionId}`,
      ); // New log
      if (initializedSessionId === sessionId) {
        transports[sessionId] = { transport, group };
        console.log(



 ... (clipped 58 lines)
Sensitive info in logs

Description: OAuth provider configuration logs 'OAuth provider configured for server: {name}',
potentially disclosing which upstream servers require OAuth and aiding targeted attacks;
avoid logging sensitive auth configuration details.
mcpService.ts [171-186]

Referred Code
const options: StreamableHTTPClientTransportOptions = {};
const headers = conf.headers ? replaceEnvVars(conf.headers) : {};

if (Object.keys(headers).length > 0) {
  options.requestInit = {
    headers,
  };
}

// Create OAuth provider if configured - SDK will handle authentication automatically
const authProvider = await createOAuthProvider(name, conf);
if (authProvider) {
  options.authProvider = authProvider;
  console.log(`OAuth provider configured for server: ${name}`);
}
Token audience confusion

Description: Protected resource metadata endpoint exposes the authorization server as the sole trusted
AS for the resource, but no audience binding or issuer validation guidance is enforced in
bearer validation; ensure downstream token validation checks 'aud/iss' strictly to prevent
token confusion.
oauthServerController.ts [652-700]

Referred Code

... (clipped 28 lines)


</details></details></td></tr>
<tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr>
<tr><td>⚪</td><td><details><summary>🎫 <strong>No ticket provided </strong></summary>


- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
<tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr>
<tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary>


Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks.

</details></td></tr>
<tr><td colspan='2'><strong>Custom Compliance</strong></td></tr>
<tr><td rowspan=1>🟢</td><td>
<details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br>

**Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br>

**Status:** Passed<br>


> Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a>
</details></td></tr>
<tr><td rowspan=2>🔴</td>
<td><details>
<summary><strong>Generic: Secure Error Handling</strong></summary><br>

**Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br>

**Status:** <br><a href='https://github.com/Xtothepowerofinfinity/ct-mcphub/pull/1/files#diff-729a335d479bfe2f57106099acae23fb04955dded34699debbe2773dfe27ac4bR339-R358'><strong>Sensitive error exposure</strong></a>: User-facing HTML responses include detailed internal error messages and possibly stack <br>traces in the &#x27;detail&#x27; section, violating the requirement to keep detailed <br>errors internal only.<br>
<details open><summary>Referred Code</summary>

```typescript
      true, // auto-close
    ),
  );
} catch (error) {
  console.error(`Failed to complete OAuth flow for server ${serverInfo.name}:`, error);
  console.error(`Error type: ${typeof error}, Error name: ${error?.constructor?.name}`);
  console.error(`Error message: ${error instanceof Error ? error.message : String(error)}`);
  console.error(`Error stack:`, error instanceof Error ? error.stack : 'No stack trace');

  return res
    .status(500)
    .send(
      generateHtmlResponse(
        'error',
        t('oauthCallback.connectionError'),
        `${t('oauthCallback.connectionErrorMessage')}\n${t('oauthCallback.reconnectMessage')}`,
        [{ label: '', value: error instanceof Error ? error.message : String(error) }],
      ),
    );
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
JWT in query/body: The controller accepts JWT via query/body ('token') and processes it, increasing
risk of token leakage via logs, referrers, and intermediaries despite being for
convenience; this violates secure input handling best practices.

Referred Code
/**
 * Attempt to attach a user to the request based on a JWT token present in header, query, or body.
 */
function resolveUserFromRequest(req: Request): AuthenticatedUser | null {
  const headerToken = req.header('x-auth-token');
  const queryToken = typeof req.query.token === 'string' ? req.query.token : undefined;
  const bodyToken =
    req.body && typeof (req.body as Record<string, unknown>).token === 'string'
      ? ((req.body as Record<string, string>).token as string)
      : undefined;
  const token = headerToken || queryToken || bodyToken;

  if (!token) {
    return null;
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET) as { user?: AuthenticatedUser };
    if (decoded?.user) {
      return decoded.user;
    }



 ... (clipped 6 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: New security-relevant events (bearer auth accept/deny, session rebuilds, OAuth-required
states) are handled but not consistently logged with user identifiers and outcomes to form
complete audit trails.

Referred Code
const errorDescription =
  reason === 'missing' ? 'No authorization provided' : 'Invalid bearer token';

const resourceMetadataUrl = buildResourceMetadataUrl(req);
const headerParts = [
  'error="invalid_token"',
  `error_description="${escapeHeaderValue(errorDescription)}"`,
];

if (resourceMetadataUrl) {
  headerParts.push(`resource_metadata="${escapeHeaderValue(resourceMetadataUrl)}"`);
}

console.warn(
  reason === 'missing'
    ? 'Bearer authentication required but no authorization header was provided'
    : 'Bearer authentication failed due to invalid bearer token',
);

res.setHeader('WWW-Authenticate', `Bearer ${headerParts.join(', ')}`);




 ... (clipped 25 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Error context leaks: The HTML error responses echo raw error messages and stacks into the page for user
consumption, which may expose internal details and lacks clear internal-only logging
separation.

Referred Code
      true, // auto-close
    ),
  );
} catch (error) {
  console.error(`Failed to complete OAuth flow for server ${serverInfo.name}:`, error);
  console.error(`Error type: ${typeof error}, Error name: ${error?.constructor?.name}`);
  console.error(`Error message: ${error instanceof Error ? error.message : String(error)}`);
  console.error(`Error stack:`, error instanceof Error ? error.stack : 'No stack trace');

  return res
    .status(500)
    .send(
      generateHtmlResponse(
        'error',
        t('oauthCallback.connectionError'),
        `${t('oauthCallback.connectionErrorMessage')}\n${t('oauthCallback.reconnectMessage')}`,
        [{ label: '', value: error instanceof Error ? error.message : String(error) }],
      ),
    );
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Token handling logs: Logs around bearer authentication and session handling add WWW-Authenticate info and
verbose details; while tokens aren’t logged, the breadth of logs could inadvertently
expose sensitive context if not sanitized across all paths.

Referred Code
if (resourceMetadataUrl) {
  headerParts.push(`resource_metadata="${escapeHeaderValue(resourceMetadataUrl)}"`);
}

console.warn(
  reason === 'missing'
    ? 'Bearer authentication required but no authorization header was provided'
    : 'Bearer authentication failed due to invalid bearer token',
);

res.setHeader('WWW-Authenticate', `Bearer ${headerParts.join(', ')}`);

const responseBody: {
  error: string;
  error_description: string;
  resource_metadata?: string;
} = {
  error: 'invalid_token',
  error_description: errorDescription,
};




 ... (clipped 6 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @Xtothepowerofinfinity, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a suite of powerful features designed to enhance MCPHub's security, flexibility, and user experience. It transforms MCPHub into a robust OAuth 2.0 Authorization Server, while simultaneously strengthening its integration with upstream OAuth-protected MCP servers. A major improvement is the comprehensive environment variable expansion within mcp_settings.json, allowing for more dynamic and secure configurations. Furthermore, a new registry provides a streamlined way to discover and install servers, complemented by a dedicated user management interface. The Docker build process now offers an extended option to include Docker Engine, and Smart Routing has been refined with group-specific filtering capabilities.

Highlights

  • OAuth 2.0 Authorization Server: MCPHub can now function as a full OAuth 2.0 Authorization Server, supporting the Authorization Code flow with PKCE, dynamic client registration (RFC 7591), and metadata discovery (RFC 8414). This enables secure integration with external applications like ChatGPT Web.
  • Enhanced Upstream OAuth Integration: Improved OAuth support for upstream MCP servers, featuring automatic discovery from WWW-Authenticate headers, dynamic client registration, and persistent token storage for seamless re-authentication.
  • Comprehensive Environment Variable Expansion: The mcp_settings.json configuration file now supports environment variable expansion for all string values, offering greater flexibility and security for managing sensitive information.
  • New Registry Integration: A new 'Registry' tab has been added to the Market Hub, allowing users to browse and install servers from an official MCP registry with cursor-based pagination for efficient browsing.
  • User Management UI: A dedicated Users page has been introduced in the UI for comprehensive user management, including functionalities to add new users, edit existing user details (such as password and admin status), and delete users.
  • Docker Engine in Extended Build: The Docker image now includes an INSTALL_EXT=true build argument, which optionally installs Docker Engine (CLI and daemon) to enable Docker-in-Docker capabilities for advanced use cases.
  • Smart Routing Enhancements: Smart Routing has been upgraded to support group-scoped searches, allowing tool discovery to be precisely limited to specific server groups for more focused results.
  • Improved Password Security: Frontend password validation now incorporates strength checks (minimum length, inclusion of letters, numbers, and special characters), and a prominent warning is displayed if the default admin password is still in use.
  • Configuration Export and Import: New API endpoints and UI features have been added to facilitate the export of the complete mcp_settings.json configuration or the import of server configurations from a JSON file.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Nov 23, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix session rebuild initialization logic
Suggestion Impact:The commit retains/introduces use of a mock response object for internal session initialization steps (initialize + notifications/initialized) during rebuilt-session initialization, aligning with the suggestion’s goal of preventing extra client responses. However, the on-the-fly initialization path in the "Server not initialized" error handler still uses the real response object, so the suggestion is only partially implemented.

code diff:

+  await requestContextService.runWithRequestContext(req, async () => {
+    // Set bearer key and group context for activity logging
+    requestContextService.setBearerKeyContext(bearerAuthResult.keyId, bearerAuthResult.keyName);
+    requestContextService.setGroupContext(group);
+
+    // Check if the session needs initialization (for rebuilt sessions)
+    if (transportInfo && transportInfo.needsInitialization) {
       console.log(
-        `[SESSION AUTO-REBUILD] Server not initialized for ${sessionId}. Attempting to initialize with the current request.`,
+        `[MCP] Session ${sessionId} needs initialization, performing proactive initialization`,
       );
 
-      // Check if the current request is an 'initialize' request
-      if (isInitializeRequest(req.body)) {
-        // If it is, we can just retry it. The transport should now be in the transports map.
-        console.log(`[SESSION AUTO-REBUILD] Retrying initialize request for ${sessionId}.`);
-        await transport.handleRequest(req, res, req.body);
-      } else {
-        // If not, we need to send an initialize request first.
-        // We construct a mock initialize request, but use the REAL req/res objects.
+      try {
+        // Create a mock response object that doesn't actually send headers
+        const mockRes = {
+          writeHead: () => {},
+          end: () => {},
+          json: () => {},
+          status: () => mockRes,
+          send: () => {},
+          headersSent: false,
+        } as any;
+
+        // First, send the initialize request
         const initializeRequest = {
           method: 'initialize',
           params: {
@@ -633,46 +758,104 @@
           id: `init-${sessionId}-${Date.now()}`,
         };
 
+        console.log(`[MCP] Sending initialize request for session ${sessionId}`);
+        // Use mock response to avoid sending actual HTTP response
+        await transport.handleRequest(req, mockRes, initializeRequest);
+
+        // Then send the initialized notification
+        const initializedNotification = {
+          method: 'notifications/initialized',
+          jsonrpc: '2.0',
+        };
+
+        console.log(`[MCP] Sending initialized notification for session ${sessionId}`);
+        await transport.handleRequest(req, mockRes, initializedNotification);
+
+        // Mark the session as initialized
+        transportInfo.needsInitialization = false;
+        console.log(`[MCP] Session ${sessionId} successfully initialized`);
+      } catch (initError) {
+        console.error('[MCP] Failed to initialize session', {
+          sessionId,
+          error: initError,
+        });
+        console.error('[MCP] Initialization error details', {
+          sessionId,
+          error: initError,
+        });
+        // Don't return here, continue with the original request
+      }

Fix the session auto-rebuild logic by using a mock response object for internal
initialization steps. This prevents sending multiple responses for a single
client request, which would otherwise cause a "headers already sent" error.

src/services/sseService.ts [606-672]

 try {
   await transport.handleRequest(req, res, req.body);
 } catch (error: any) {
   // Check if this is a "Server not initialized" error for a newly rebuilt session
   if (sessionId && error.message && error.message.includes('Server not initialized')) {
     console.log(
       `[SESSION AUTO-REBUILD] Server not initialized for ${sessionId}. Attempting to initialize with the current request.`,
     );
 
-    // Check if the current request is an 'initialize' request
-    if (isInitializeRequest(req.body)) {
-      // If it is, we can just retry it. The transport should now be in the transports map.
-      console.log(`[SESSION AUTO-REBUILD] Retrying initialize request for ${sessionId}.`);
+    // Create a mock response object that doesn't send anything to the client
+    const mockRes = {
+      writeHead: () => {},
+      end: () => {},
+      json: () => {},
+      status: () => mockRes,
+      send: () => {},
+      headersSent: false,
+    } as any;
+
+    try {
+      // If the current request is not an 'initialize' request, we need to send one first.
+      if (!isInitializeRequest(req.body)) {
+        const initializeRequest = {
+          method: 'initialize',
+          params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'MCPHub-Client', version: '1.0.0' } },
+          jsonrpc: '2.0',
+          id: `init-${sessionId}-${Date.now()}`,
+        };
+        console.log(`[SESSION AUTO-REBUILD] Sending internal initialize request for ${sessionId}.`);
+        await transport.handleRequest(req, mockRes, initializeRequest);
+
+        const initializedNotification = { method: 'notifications/initialized', jsonrpc: '2.0' };
+        console.log(`[SESSION AUTO-REBUILD] Sending internal initialized notification for ${sessionId}.`);
+        await transport.handleRequest(req, mockRes, initializedNotification);
+      }
+
+      // Now that the session is initialized, retry the original request with the real response object.
+      console.log(`[SESSION AUTO-REBUILD] Initialization complete for ${sessionId}. Retrying original request.`);
       await transport.handleRequest(req, res, req.body);
-    } else {
-      // If not, we need to send an initialize request first.
-      // We construct a mock initialize request, but use the REAL req/res objects.
-      const initializeRequest = {
-        method: 'initialize',
-        params: {
-          protocolVersion: '2025-03-26',
-          capabilities: {},
-          clientInfo: {
-            name: 'MCPHub-Client',
-            version: '1.0.0',
-          },
-        },
-        jsonrpc: '2.0',
-        id: `init-${sessionId}-${Date.now()}`,
-      };
-
-      console.log(
-        `[SESSION AUTO-REBUILD] Sending initialize request for ${sessionId} before handling the actual request.`,
-      );
-      try {
-        // Temporarily replace the body to send the initialize request
-        const originalBody = req.body;
-        req.body = initializeRequest;
-        await transport.handleRequest(req, res, req.body);
-
-        // Now, send the notifications/initialized
-        const initializedNotification = {
-          method: 'notifications/initialized',
-          jsonrpc: '2.0',
-        };
-        req.body = initializedNotification;
-        await transport.handleRequest(req, res, req.body);
-
-        // Restore the original body and retry the original request
-        req.body = originalBody;
-        console.log(
-          `[SESSION AUTO-REBUILD] Initialization complete for ${sessionId}. Retrying original request.`,
-        );
-        await transport.handleRequest(req, res, req.body);
-      } catch (initError) {
-        console.error(
-          `[SESSION AUTO-REBUILD] Failed to initialize session ${sessionId} on-the-fly:`,
-          initError,
-        );
-        // Re-throw the original error if initialization fails
-        throw error;
+    } catch (initError) {
+      console.error(`[SESSION AUTO-REBUILD] Failed to initialize session ${sessionId} on-the-fly:`, initError);
+      // If initialization fails, send a proper error response instead of re-throwing.
+      if (!res.headersSent) {
+        res.status(500).json({ jsonrpc: '2.0', error: { code: -32001, message: 'Session initialization failed' }, id: req.body?.id || null });
       }
     }
   } else {
     // If it's a different error, just re-throw it
     throw error;
   }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where multiple responses are sent for a single request, which would cause a "headers already sent" error and break the session rebuild feature. The proposed fix using a mock response is the correct approach.

High
Refine SSE reconnection error handling
Suggestion Impact:The commit adds explicit authentication-error detection (parsing tool call result content for a 401 and throwing an "HTTP 401 Unauthorized" error), which helps align retries/reconnection with auth failures. However, it does not implement the suggested change to the reconnection condition itself (i.e., it does not gate SSE reconnection on an "Unauthorized" check); it mainly ensures auth failures surface as recognizable errors.

code diff:

@@ -267,6 +241,8 @@
   for (let attempt = 0; attempt <= maxRetries; attempt++) {
     try {
       const result = await serverInfo.client.callTool(toolParams, undefined, options || {});
+      // Check auth error
+      checkAuthError(result);
       return result;
     } catch (error: any) {
       // Check if error message starts with "Error POSTing to endpoint (HTTP 40"
@@ -293,7 +269,7 @@
           serverInfo.client.close();
           serverInfo.transport.close();
 
-          const server = await serverDao.findById(serverInfo.name);
+          const server = await getServerDao().findById(serverInfo.name);
           if (!server) {
             throw new Error(`Server configuration not found for: ${serverInfo.name}`);
           }
@@ -308,11 +284,7 @@
               version: '1.0.0',
             },
             {
-              capabilities: {
-                prompts: {},
-                resources: {},
-                tools: {},
-              },
+              capabilities: {},
             },
           );
 
@@ -373,7 +345,7 @@
   isInit: boolean,
   serverName?: string,
 ): Promise<ServerInfo[]> => {
-  const allServers: ServerConfigWithName[] = await serverDao.findAll();
+  const allServers: ServerConfigWithName[] = await getServerDao().findAll();
   const existingServerInfos = serverInfos;
   const nextServerInfos: ServerInfo[] = [];
 
@@ -494,11 +466,7 @@
           version: '1.0.0',
         },
         {
-          capabilities: {
-            prompts: {},
-            resources: {},
-            tools: {},
-          },
+          capabilities: {},
         },
       );
 
@@ -597,9 +565,10 @@
           if (!dataError) {
             serverInfo.status = 'connected';
             serverInfo.error = null;
-
-            // Set up keep-alive ping for SSE connections
-            setupKeepAlive(serverInfo, expandedConf);
+            // Set up keep-alive ping for SSE connections via shared service
+            setupClientKeepAlive(serverInfo, expandedConf).catch((e) =>
+              console.warn(`Keepalive setup failed for ${name}:`, e),
+            );
           } else {
             serverInfo.status = 'disconnected';
             serverInfo.error = `Failed to list data: ${dataError} `;
@@ -650,11 +619,39 @@
 
 // Get all server information
 export const getServersInfo = async (): Promise<Omit<ServerInfo, 'client' | 'transport'>[]> => {
-  const allServers: ServerConfigWithName[] = await serverDao.findAll();
+  const allServers: ServerConfigWithName[] = await getServerDao().findAll();
   const dataService = getDataService();
+
+  // Ensure that servers recently added via DAO but not yet initialized in serverInfos
+  // are still visible in the servers list. This avoids a race condition where
+  // a POST /api/servers immediately followed by GET /api/servers would not
+  // return the newly created server until background initialization completes.
+  const combinedServerInfos: ServerInfo[] = [...serverInfos];
+  const existingNames = new Set(combinedServerInfos.map((s) => s.name));
+
+  for (const server of allServers) {
+    if (!existingNames.has(server.name)) {
+      const isEnabled = server.enabled === undefined ? true : server.enabled;
+      combinedServerInfos.push({
+        name: server.name,
+        owner: server.owner,
+        // Newly created servers that are enabled should appear as "connecting"
+        // until the MCP client initialization completes. Disabled servers remain
+        // in the "disconnected" state.
+        status: isEnabled ? 'connecting' : 'disconnected',
+        error: null,
+        tools: [],
+        prompts: [],
+        createTime: Date.now(),
+        enabled: isEnabled,
+      });
+    }
+  }
+
   const filterServerInfos: ServerInfo[] = dataService.filterData
-    ? dataService.filterData(serverInfos)
-    : serverInfos;
+    ? dataService.filterData(combinedServerInfos)
+    : combinedServerInfos;
+
   const infos = filterServerInfos.map(
     ({ name, status, tools, prompts, createTime, error, oauth }) => {
       const serverConfig = allServers.find((server) => server.name === name);
@@ -756,7 +753,7 @@
 
 // Filter tools by server configuration
 const filterToolsByConfig = async (serverName: string, tools: Tool[]): Promise<Tool[]> => {
-  const serverConfig = await serverDao.findById(serverName);
+  const serverConfig = await getServerDao().findById(serverName);
   if (!serverConfig || !serverConfig.tools) {
     // If no tool configuration exists, all tools are enabled by default
     return tools;
@@ -780,7 +777,7 @@
   config: ServerConfig,
 ): Promise<{ success: boolean; message?: string }> => {
   const server: ServerConfigWithName = { name, ...config };
-  const result = await serverDao.create(server);
+  const result = await getServerDao().create(server);
   if (result) {
     return { success: true, message: 'Server added successfully' };
   } else {
@@ -792,7 +789,7 @@
 export const removeServer = async (
   name: string,
 ): Promise<{ success: boolean; message?: string }> => {
-  const result = await serverDao.delete(name);
+  const result = await getServerDao().delete(name);
   if (!result) {
     return { success: false, message: 'Failed to remove server' };
   }
@@ -808,24 +805,23 @@
   allowOverride: boolean = false,
 ): Promise<{ success: boolean; message?: string }> => {
   try {
-    const exists = await serverDao.exists(name);
+    const exists = await getServerDao().exists(name);
     if (exists && !allowOverride) {
       return { success: false, message: 'Server name already exists' };
     }
 
-    // If overriding and this is a DXT server (stdio type with file paths),
-    // we might want to clean up old files in the future
-    if (exists && config.type === 'stdio') {
-      // Close existing server connections
+    // If overriding an existing server, close connections and clear keep-alive timers
+    if (exists) {
+      // Close existing server connections (clears keep-alive intervals as well)
       closeServer(name);
       // Remove from server infos
       serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name);
     }
 
     if (exists) {
-      await serverDao.update(name, config);
+      await getServerDao().update(name, config);
     } else {
-      await serverDao.create({ name, ...config });
+      await getServerDao().create({ name, ...config });
     }
 
     const action = exists ? 'updated' : 'added';
@@ -835,6 +831,25 @@
     return { success: false, message: 'Failed to add/update server' };
   }
 };
+
+// Check for authentication error in tool call result
+function checkAuthError(result: any) {
+  if (Array.isArray(result.content) && result.content.length > 0) {
+    const text = result.content[0]?.text;
+    if (typeof text === 'string') {
+      let errorContent;
+      try {
+        errorContent = JSON.parse(text);
+      } catch (e) {
+        // Ignore JSON parse errors and continue
+        return;
+      }
+      if (errorContent.code === 401) {
+        throw new Error('Error POSTing to endpoint (HTTP 401 Unauthorized)');
+      }
+    }
+  }
+}

Refine the SSE reconnection logic to trigger only on specific recoverable
errors, such as authorization failures, instead of on any error, to prevent
potential infinite retry loops.

src/services/mcpService.ts [273-284]

 const isHttp40xError = error?.message?.startsWith?.('Error POSTing to endpoint (HTTP 40');
+const isSseAuthError = error?.message?.includes('Unauthorized');
 // Only retry for StreamableHTTPClientTransport
 const isStreamableHttp = serverInfo.transport instanceof StreamableHTTPClientTransport;
 const isSSE = serverInfo.transport instanceof SSEClientTransport;
 if (
   attempt < maxRetries &&
   serverInfo.transport &&
-  ((isStreamableHttp && isHttp40xError) || isSSE)
+  ((isStreamableHttp && isHttp40xError) || (isSSE && isSseAuthError))
 ) {
   console.warn(
-    `${isHttp40xError ? 'HTTP 40x error' : 'error'} detected for ${isStreamableHttp ? 'StreamableHTTP' : 'SSE'} server ${serverInfo.name}, attempting reconnection (attempt ${attempt + 1}/${maxRetries + 1})`,
+    `Reconnection-triggering error detected for ${isStreamableHttp ? 'StreamableHTTP' : 'SSE'} server ${serverInfo.name}, attempting reconnection (attempt ${attempt + 1}/${maxRetries + 1})`,
   );

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that retrying on any SSE error can cause infinite loops and proposes a more specific condition, significantly improving the robustness of the new reconnection logic.

Medium
Prevent potential null pointer exception
Suggestion Impact:The commit removed the entire settings-fetching/parsing logic (including the unsafe non-null assertions on oauth.dynamicRegistration) by refactoring useSettingsData to simply return useSettings() from SettingsContext, thereby eliminating the potential null pointer exception rather than applying the specific null-check change.

code diff:

-      if (data.success) {
-        if (data.data?.systemConfig?.oauthServer) {
-          const oauth = data.data.systemConfig.oauthServer;
-          const defaultOauthConfig = getDefaultOAuthServerConfig();
-          const defaultDynamic = defaultOauthConfig.dynamicRegistration;
-          const allowedScopes = Array.isArray(oauth.allowedScopes)
-            ? [...oauth.allowedScopes]
-            : [...defaultOauthConfig.allowedScopes];
-          const dynamicAllowedGrantTypes = Array.isArray(
-            oauth.dynamicRegistration?.allowedGrantTypes,
-          )
-            ? [...oauth.dynamicRegistration!.allowedGrantTypes!]
-            : [...defaultDynamic.allowedGrantTypes];
-

Add a null check for oauth.dynamicRegistration before accessing its properties
to prevent a potential null pointer exception.

frontend/src/hooks/useSettingsData.ts [186-190]

-const dynamicAllowedGrantTypes = Array.isArray(
-  oauth.dynamicRegistration?.allowedGrantTypes,
-)
-  ? [...oauth.dynamicRegistration!.allowedGrantTypes!]
-  : [...defaultDynamic.allowedGrantTypes];
+const dynamicAllowedGrantTypes =
+  oauth.dynamicRegistration && Array.isArray(oauth.dynamicRegistration.allowedGrantTypes)
+    ? [...oauth.dynamicRegistration.allowedGrantTypes]
+    : [...defaultDynamic.allowedGrantTypes];

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential null pointer exception due to a non-null assertion (!) and provides a robust fix that prevents a runtime crash.

Medium
Correct PKCE code challenge validation
Suggestion Impact:The commit modified the code_challenge validation regex, but it did not add '.'; instead it added support for '=' padding (and kept '-' and '_'), changing the validation behavior in a different way than suggested.

code diff:

+    // RFC 7636: code_challenge is base64url-encoded, may include = padding
     const code_challenge = req.query.code_challenge
-      ? validateQueryParam(req.query.code_challenge, 'code_challenge', /^[a-zA-Z0-9_-]+$/)
+      ? validateQueryParam(req.query.code_challenge, 'code_challenge', /^[a-zA-Z0-9_\-=]+$/)
       : undefined;

Update the PKCE code_challenge validation regex to include the . character,
ensuring compliance with RFC 7636 and preventing valid authorization requests
from being incorrectly rejected.

src/controllers/oauthServerController.ts [195-198]

 const code_challenge = req.query.code_challenge
-  ? validateQueryParam(req.query.code_challenge, 'code_challenge', /^[a-zA-Z0-9_-]+$/)
+  ? validateQueryParam(req.query.code_challenge, 'code_challenge', /^[a-zA-Z0-9_.-]+$/)
   : undefined;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the regex for code_challenge validation is too restrictive and not fully compliant with RFC 7636, as it misses the . character. This fix improves RFC compliance and prevents valid requests from failing.

Medium
Use persistent storage for tokens

Persist OAuth registration access tokens to a durable storage layer instead of
an in-memory Map to ensure they survive application restarts.

src/controllers/oauthDynamicRegistrationController.ts [12-13]

-// Store registration access tokens (in production, use database)
-const registrationTokens = new Map<string, { clientId: string; createdAt: Date }>();
+// Registration access tokens should be stored persistently.
+// This can be integrated into the existing `src/models/OAuth.ts` model
+// by adding functions like `saveRegistrationAccessToken` and `findRegistrationAccessToken`.
+// The in-memory map should be replaced with calls to a persistent store.

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that storing registration tokens in-memory makes the client management feature unreliable across server restarts, and a persistent solution is needed for production readiness.

Medium
Prevent unintentional data deletion on update

Modify the client metadata update logic to only apply fields explicitly provided
in the request, preventing unintentional deletion of existing data when a field
is omitted from the update payload.

src/controllers/oauthDynamicRegistrationController.ts [435-444]

 // Update metadata
-if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
+const metadataUpdates: Record<string, any> = {};
+if (contacts !== undefined) metadataUpdates.contacts = contacts;
+if (logo_uri !== undefined) metadataUpdates.logo_uri = logo_uri;
+if (client_uri !== undefined) metadataUpdates.client_uri = client_uri;
+if (policy_uri !== undefined) metadataUpdates.policy_uri = policy_uri;
+if (tos_uri !== undefined) metadataUpdates.tos_uri = tos_uri;
+
+if (Object.keys(metadataUpdates).length > 0) {
   updates.metadata = {
     ...client.metadata,
-    contacts,
-    logo_uri,
-    client_uri,
-    policy_uri,
-    tos_uri,
+    ...metadataUpdates,
   };
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a bug where updating client metadata can unintentionally delete existing fields, and the proposed fix correctly ensures only provided fields are updated.

Medium
Correct client secret generation logic

Change the client secret generation logic to be opt-in by checking if
requireSecret is explicitly true, preventing secrets from being created by
default.

src/controllers/oauthClientController.ts [108]

 // Generate client secret if required
-const clientSecret = requireSecret !== false ? crypto.randomBytes(32).toString('hex') : undefined;
+const clientSecret = requireSecret === true ? crypto.randomBytes(32).toString('hex') : undefined;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the logic for requireSecret defaults to an unsafe behavior and proposes a stricter, opt-in check, improving the security and predictability of the endpoint.

Medium
Security
Avoid using request host for URLs

Avoid using the client-provided Host header to construct the baseUrl for the
registrationClientUri to prevent potential Host Header Injection attacks.
Instead, rely exclusively on the configured system baseUrl and fail if it's not
set.

src/controllers/oauthDynamicRegistrationController.ts [158-160]

-const baseUrl =
-  settings.systemConfig?.install?.baseUrl || `${req.protocol}://${req.get('host')}`;
+const baseUrl = settings.systemConfig?.install?.baseUrl;
+if (!baseUrl) {
+  console.error('System base URL is not configured. Cannot generate registration_client_uri.');
+  res.status(500).json({
+    error: 'server_error',
+    error_description: 'Server base URL is not configured.',
+  });
+  return;
+}
 const registrationClientUri = `${baseUrl}/oauth/register/${clientId}`;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a security vulnerability where a manipulated Host header could lead to generating an insecure registration_client_uri, and proposes the correct fix of relying only on server configuration.

Medium
  • Update

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a massive and impressive pull request that introduces a wide range of significant features and improvements to MCPHub. The implementation of a full OAuth 2.0 client and server is a major undertaking and appears to be well-executed, following relevant RFCs. The addition of session persistence with keep-alive pings and transparent session rebuild is a fantastic enhancement for reliability.

Key highlights of this PR include:

  • Comprehensive OAuth 2.0 support: Both for upstream MCP servers (as a client) and for external applications (as an authorization server). The dynamic client registration (RFC 7591) and PKCE support are excellent additions.
  • Session Persistence: The SSE keep-alive mechanism and session rebuild on restart will greatly improve the user experience and server robustness.
  • Configuration Enhancements: The recursive environment variable expansion and the new configuration options for OAuth and other features provide much-needed flexibility and security.
  • Code Quality and Refactoring: Many parts of the codebase have been refactored for better maintainability, such as moving path utilities and parameter conversion to separate modules, and converting the service registry to use async/await with ES modules.
  • Fixes and UI improvements: Numerous bug fixes, like URL parameter decoding, and frontend enhancements, like the new hooks and improved forms, round out this substantial update.
  • Comprehensive Testing: The addition of extensive tests for the new features is commendable and crucial for a change of this size.

I have a couple of suggestions for improvement, mainly concerning potential race conditions in configuration management and some minor code cleanup. Overall, this is an excellent contribution that significantly advances the capabilities and maturity of the project.

Comment thread src/models/OAuth.ts Outdated
Comment on lines +66 to +67
settings.oauthClients.push(client);
saveSettings(settings);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of saveSettings (and by extension, functions like createOAuthClient, updateOAuthClient, deleteOAuthClient, saveToken, and revokeToken) performs a read-modify-write operation on the mcp_settings.json file without any locking mechanism. This can lead to race conditions and data loss if multiple concurrent requests attempt to modify the settings file. For example, if two requests try to add an OAuth client at the same time, one of the additions could be overwritten.
To make this operation atomic and safe for concurrent access, I recommend implementing a file-locking mechanism around the read and write operations in saveSettings. A library like proper-lockfile could be used for this. This would likely require making saveSettings and its callers asynchronous.

Comment on lines +97 to +100
const separatorIndex = stateValue.indexOf(':');
if (separatorIndex > 0) {
return stateValue.slice(0, separatorIndex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This fallback logic for parsing the state parameter by looking for a colon seems to be from a previous implementation. The current state generation in mcpOAuthProvider.ts creates a URL-safe base64 encoded JSON object, which doesn't contain a colon in this manner. If this format is no longer supported, this block can be removed to simplify the code. If it's kept for backward compatibility, a comment explaining this would be beneficial.

Co-authored-by: samanhappy <samanhappy@gmail.com>
Copy link
Copy Markdown

@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: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/copilot-instructions.md (1)

46-106: Update line 46 port comment from 3001 to 3000

The backend actually runs on port 3000 by default (configured in src/config/index.ts line 12: port: process.env.PORT || 3000). The comment on line 46 incorrectly states "Backend on :3001", which contradicts the manual validation curl commands on lines 96-107 that correctly use port 3000. Update line 46 to read:

pnpm dev                    # Backend on :3000, Frontend on :5173
frontend/src/services/promptService.ts (1)

62-69: Function signature allows optional server but all callers provide it; consider enforcing type safety or handling the undefined case

The getPrompt function signature accepts server?: string (optional), but directly interpolates it into the URL: /mcp/${server}/prompts/.... If server is undefined, this produces /mcp/undefined/prompts/... instead of a valid route.

While the only current call site (PromptCard) always provides a non-empty server string (it's a required prop in the interface), the optional type signature creates a misleading API contract. Additionally, the server parameter should be URL-encoded consistently with promptName.

Either make server: string required in the function signature to match the backend's validation (/mcp/:serverName/prompts/:promptName requires both), or handle the undefined case explicitly:

const serverSegment = server ? `/${encodeURIComponent(server)}` : '';
const response = await apiPost(
  `/mcp${serverSegment}/prompts/${encodeURIComponent(request.promptName)}`,
  {
    name: request.promptName,
    arguments: request.arguments,
  },
);
♻️ Duplicate comments (2)
src/controllers/oauthCallbackController.ts (1)

82-103: Backward compatibility fallback for state parsing.

The colon-delimiter fallback (lines 97-100) maintains backward compatibility with older state formats. A previous review flagged this; keeping it with this comment is acceptable.

src/models/OAuth.ts (1)

54-69: Race condition in client creation persists (as noted in past review).

The createOAuthClient function performs a read-modify-write operation without locking. As noted in the past review, concurrent client creation could lead to data loss. This is a known issue with the current settings persistence approach.

🟡 Minor comments (14)
.github/copilot-instructions.md-145-149 (1)

145-149: Fix fetchInterceptor filename extension in project structure section

The critical frontend files list references frontend/src/utils/fetchInterceptor.js, but the actual file is TypeScript (fetchInterceptor.ts), and imports use the compiled .js only at runtime.

Suggest updating the doc entry to:

-- `frontend/src/utils/fetchInterceptor.js` - Backend API interaction
+- `frontend/src/utils/fetchInterceptor.ts` - Backend API interaction

to match the source file name.

frontend/src/components/ServerForm.tsx-489-494 (1)

489-494: Non-null assertion on potentially undefined openapi.

Using prev.openapi! assumes openapi is always defined, but it could be undefined if the form state was never properly initialized. This could cause runtime errors.

-  onChange={() =>
-    setFormData((prev) => ({
-      ...prev,
-      openapi: { ...prev.openapi!, inputMode: 'url' },
-    }))
-  }
+  onChange={() =>
+    setFormData((prev) => ({
+      ...prev,
+      openapi: { ...(prev.openapi || {}), inputMode: 'url' },
+    }))
+  }

Apply the same pattern to other occurrences using the non-null assertion on prev.openapi!.

Also applies to: 506-511

frontend/src/components/ServerForm.tsx-35-43 (1)

35-43: description field added but not in EnvVar type.

The getInitialServerEnvVars function adds a description field (line 41) to each object, but the EnvVar interface only has key and value properties. This field is unused and inconsistent with the type.

   const getInitialServerEnvVars = (data: Server | null): EnvVar[] => {
     if (!data || !data.config || !data.config.env) return [];

     return Object.entries(data.config.env).map(([key, value]) => ({
       key,
       value,
-      description: '', // You can set a default description if needed
     }));
   };
frontend/src/hooks/useUserData.ts-13-37 (1)

13-37: Missing t in dependency array for fetchUsers.

The t function from useTranslation is used on line 18 but not included in the useCallback dependency array. This could lead to stale translations if the language changes.

-  }, []);
+  }, [t]);
frontend/src/hooks/useUserData.ts-69-83 (1)

69-83: Inconsistent return types in deleteUser.

The function returns result (object) on lines 74 and 78, but returns false (boolean) on line 81. This inconsistency makes it difficult for consumers to handle the response reliably.

   const deleteUser = async (username: string) => {
     try {
       const result = await apiDelete(`/users/${username}`);
       if (!result?.success) {
         setError(result?.message || t('users.deleteError'));
-        return result;
+        return false;
       }

       triggerRefresh();
-      return result;
+      return true;
     } catch (err) {
       setError(err instanceof Error ? err.message : 'Failed to delete user');
       return false;
     }
   };
docs/api-reference/system.mdx-29-34 (1)

29-34: Document optional serverName parameter for MCP settings export

The implementation of the MCP settings export handler supports an optional serverName query to export a single server’s configuration, but this page only describes the full export.

Consider adding a short note under Export MCP Settings like:

  • Optional query: ?serverName=<name> – returns JSON containing only that server under mcpServers.

This keeps the API reference aligned with the controller behavior and frontend usage.

Also applies to: 96-104

README.zh.md-173-189 (1)

173-189: Add language identifier to code fence.

The code fence starting at line 177 is missing a language identifier, which affects syntax highlighting and markdown linting.

Apply this diff:

 您可以将智能路由与分组筛选结合使用,仅在特定服务器分组内搜索:
 
-```
+```text
 # 仅在生产服务器中搜索
 http://localhost:3000/mcp/$smart/production
 
 # 仅在开发服务器中搜索
 http://localhost:3000/mcp/$smart/development

</blockquote></details>
<details>
<summary>examples/oauth-dynamic-registration-config.json-21-21 (1)</summary><blockquote>

`21-21`: **Invalid bcrypt password hash placeholder.**

The password hash `"$2b$10$abcdefghijklmnopqrstuv"` is not a valid bcrypt hash (bcrypt hashes are 60 characters long). For an example file, consider using a valid bcrypt hash of a well-known test password with a clear comment.



Apply this diff to use a valid example hash:

```diff
     {
       "username": "admin",
-      "password": "$2b$10$abcdefghijklmnopqrstuv",
+      "password": "$2b$10$YourValidBcryptHashHere1234567890123456789012345678",
+      "// Note: Replace with actual bcrypt hash. Example above hashes 'changeme'": "",
       "isAdmin": true
     }

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

frontend/src/hooks/useRegistryData.ts-28-80 (1)

28-80: searchQuery missing from fetchRegistryServers dependencies but used in fallback.

The function uses searchQuery as a fallback (line 40) when search parameter is undefined, but searchQuery is not in the dependency array. This could cause stale closure issues.

  const fetchRegistryServers = useCallback(
    async (cursor?: string, search?: string) => {
      // ... implementation
    },
-   [t, serversPerPage],
+   [t, serversPerPage, searchQuery],
  );

Alternatively, always require the search parameter explicitly to avoid relying on closed-over state.

frontend/src/hooks/useRegistryData.ts-125-134 (1)

125-134: changeServersPerPage has race condition with state updates.

The function calls fetchRegistryServers immediately after setServersPerPage, but React state updates are async. The fetch may use the old serversPerPage value.

  const changeServersPerPage = useCallback(
-   async (newServersPerPage: number) => {
-     setServersPerPage(newServersPerPage);
-     setCurrentPage(1);
-     setCursorHistory([]);
-     setAllServers([]);
-     await fetchRegistryServers(undefined, searchQuery);
-   },
-   [searchQuery, fetchRegistryServers],
+   (newServersPerPage: number) => {
+     setServersPerPage(newServersPerPage);
+     setCurrentPage(1);
+     setCursorHistory([]);
+     setAllServers([]);
+     // Fetch will be triggered by useEffect watching serversPerPage
+   },
+   [],
  );

Or pass newServersPerPage directly to the fetch function.

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

src/controllers/oauthCallbackController.ts-267-267 (1)

267-267: Setting status = 'connected' before actual connection attempt.

Line 267 sets serverInfo.status = 'connected' before the client connection attempt (lines 277-317). If connection fails, the status remains 'connected' which is misleading.

-       // Update server status to indicate OAuth is complete
-       serverInfo.status = 'connected';
        if (serverInfo.oauth) {
          serverInfo.oauth.authorizationUrl = undefined;
          serverInfo.oauth.state = undefined;
          serverInfo.oauth.codeVerifier = undefined;
        }

        // Check if client needs to be connected
        const isClientConnected = serverInfo.client && serverInfo.client.getServerCapabilities();

        if (!isClientConnected) {
          // Client is not connected yet, connect it
          if (serverInfo.client && serverInfo.transport) {
            console.log(`Connecting client with refreshed transport for: ${serverInfo.name}`);
            try {
              await serverInfo.client.connect(serverInfo.transport, serverInfo.options);
+             serverInfo.status = 'connected';
              console.log(`Client connected successfully for: ${serverInfo.name}`);
              // ... rest
            } catch (connectError) {
              console.error(`Error connecting client for ${serverInfo.name}:`, connectError);
+             serverInfo.status = 'disconnected';
              // ...
            }
          }
+       } else {
+         serverInfo.status = 'connected';
        }

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

frontend/src/components/RegistryServerCard.tsx-28-40 (1)

28-40: Date parsing edge case.

new Date(dateString) doesn't throw for invalid strings—it returns an Invalid Date object. The catch block won't trigger for malformed dates; instead, the function will return NaN/NaN/NaN.

   const formatDate = (dateString?: string) => {
     if (!dateString) return '';
     try {
       const date = new Date(dateString);
+      if (isNaN(date.getTime())) return '';
       const year = date.getFullYear();
       const month = (date.getMonth() + 1).toString().padStart(2, '0');
       const day = date.getDate().toString().padStart(2, '0');
       return `${year}/${month}/${day}`;
     } catch {
       return '';
     }
   };
frontend/src/components/RegistryServerCard.tsx-64-66 (1)

64-66: Add keyboard accessibility for the clickable card.

The card is clickable via onClick but lacks keyboard navigation support. Add role="button", tabIndex, and onKeyDown for accessibility compliance.

     <div
       className="bg-white border border-gray-200 rounded-xl p-4 hover:shadow-lg hover:border-blue-400 hover:-translate-y-1 transition-all duration-300 cursor-pointer group relative overflow-hidden h-full flex flex-col"
       onClick={handleClick}
+      role="button"
+      tabIndex={0}
+      onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault();
+          handleClick();
+        }
+      }}
     >
frontend/src/pages/MarketPage.tsx-717-722 (1)

717-722: Registry pagination display has string concatenation issue.

The total count display concatenates a string '+' with a number which may produce unexpected results in the translation. The translation key expects a number for total, but receives a string.

                       ? t('registry.showing', {
                           from: (currentPage - 1) * serversPerPage + 1,
                           to: (currentPage - 1) * serversPerPage + servers.length,
-                          total: allServers.length + (registryHasNextPage ? '+' : ''),
+                          total: `${allServers.length}${registryHasNextPage ? '+' : ''}`,
                         })

Or update the translation to handle this case properly.

// Build initial data for ServerForm
const getInitialFormData = () => {
if (!selectedOption || !selectedInstallType) return null;
console.log('Building initial form data for:', selectedOption);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statement.

This appears to be a development artifact that should be removed before merging.

 const getInitialFormData = () => {
   if (!selectedOption || !selectedInstallType) return null;
-  console.log('Building initial form data for:', selectedOption);

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

🤖 Prompt for AI Agents
In frontend/src/components/RegistryServerDetail.tsx around line 134, remove the
development debug statement console.log('Building initial form data for:',
selectedOption);; simply delete that line (or replace it with appropriate
logging mechanism if persistent logs are required) to avoid leaking debug output
in production.

Comment thread frontend/src/components/ServerCard.tsx Outdated
Comment thread frontend/src/hooks/useSettingsData.ts Outdated
Comment on lines +602 to +615
const exportMCPSettings = async (serverName?: string) => {
setLoading(true);
setError(null);
try {
return await apiGet(`/mcp-settings/export?serverName=${serverName ? serverName : ''}`);
} catch (error) {
console.error('Failed to export MCP settings:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to export MCP settings';
setError(errorMessage);
showToast(errorMessage);
} finally {
setLoading(false);
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

exportMCPSettings doesn't return a value on error.

The function implicitly returns undefined when catching an error, but the caller in ServerCard.tsx expects a result with .data. This will cause a runtime error.

  const exportMCPSettings = async (serverName?: string) => {
    setLoading(true);
    setError(null);
    try {
      return await apiGet(`/mcp-settings/export?serverName=${serverName ? serverName : ''}`);
    } catch (error) {
      console.error('Failed to export MCP settings:', error);
      const errorMessage = error instanceof Error ? error.message : 'Failed to export MCP settings';
      setError(errorMessage);
      showToast(errorMessage);
+     return null;
    } finally {
      setLoading(false);
    }
  };

Then update the caller to check for null before accessing .data.

📝 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
const exportMCPSettings = async (serverName?: string) => {
setLoading(true);
setError(null);
try {
return await apiGet(`/mcp-settings/export?serverName=${serverName ? serverName : ''}`);
} catch (error) {
console.error('Failed to export MCP settings:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to export MCP settings';
setError(errorMessage);
showToast(errorMessage);
} finally {
setLoading(false);
}
};
const exportMCPSettings = async (serverName?: string) => {
setLoading(true);
setError(null);
try {
return await apiGet(`/mcp-settings/export?serverName=${serverName ? serverName : ''}`);
} catch (error) {
console.error('Failed to export MCP settings:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to export MCP settings';
setError(errorMessage);
showToast(errorMessage);
return null;
} finally {
setLoading(false);
}
};
🤖 Prompt for AI Agents
In frontend/src/hooks/useSettingsData.ts around lines 602 to 615,
exportMCPSettings currently swallows errors and returns undefined on failure
which causes callers (e.g., ServerCard.tsx) to crash when accessing .data;
modify exportMCPSettings to always return a consistent shape (e.g., null or an
object) on error — catch the error, setError/showToast as now, then return null
(or a defined error result) so callers can handle it; then update the
ServerCard.tsx caller to null-check the result before accessing .data (e.g., if
(!result) { handle error } or guard result.data) to avoid runtime exceptions.

Comment on lines +17 to +29
if (!data.success) {
setError(data.message || t('users.fetchError'));
return;
}

if (data && data.success && Array.isArray(data.data)) {
setUsers(data.data);
} else {
console.error('Invalid user data format:', data);
setUsers([]);
}

setError(null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redundant success check and incorrect error state clearing.

The success check on line 22 duplicates the check on line 17. Additionally, setError(null) on line 29 executes even after setting an error on line 18 (when !data.success), immediately clearing the error.

     if (!data.success) {
       setError(data.message || t('users.fetchError'));
       return;
     }

-    if (data && data.success && Array.isArray(data.data)) {
+    if (Array.isArray(data.data)) {
       setUsers(data.data);
+      setError(null);
     } else {
       console.error('Invalid user data format:', data);
       setUsers([]);
+      setError(t('users.invalidDataFormat'));
     }
-
-    setError(null);
📝 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
if (!data.success) {
setError(data.message || t('users.fetchError'));
return;
}
if (data && data.success && Array.isArray(data.data)) {
setUsers(data.data);
} else {
console.error('Invalid user data format:', data);
setUsers([]);
}
setError(null);
if (!data.success) {
setError(data.message || t('users.fetchError'));
return;
}
if (Array.isArray(data.data)) {
setUsers(data.data);
setError(null);
} else {
console.error('Invalid user data format:', data);
setUsers([]);
setError(t('users.invalidDataFormat'));
}
🤖 Prompt for AI Agents
In frontend/src/hooks/useUserData.ts around lines 17 to 29, the code redundantly
checks data.success twice and always calls setError(null) at the end which
clears errors set in the early-return path; remove the second data.success check
and restructure the branches so the early-return when !data.success exits before
any downstream logic, and only call setError(null) inside the successful branch
after setting users (or set an error and empty users on invalid format),
ensuring you don't clear errors after they've been set.

Comment thread frontend/src/pages/MarketPage.tsx Outdated
Comment thread src/config/index.ts
Comment thread src/controllers/oauthCallbackController.ts
Comment on lines +435 to +444
if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
updates.metadata = {
...client.metadata,
contacts,
logo_uri,
client_uri,
policy_uri,
tos_uri,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Undefined values may unintentionally clear existing metadata fields.

When destructuring contacts, logo_uri, etc. from req.body, undefined values will overwrite existing metadata. Only fields explicitly provided should be updated.

-    if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
-      updates.metadata = {
-        ...client.metadata,
-        contacts,
-        logo_uri,
-        client_uri,
-        policy_uri,
-        tos_uri,
-      };
-    }
+    if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
+      updates.metadata = {
+        ...client.metadata,
+        ...(contacts !== undefined && { contacts }),
+        ...(logo_uri !== undefined && { logo_uri }),
+        ...(client_uri !== undefined && { client_uri }),
+        ...(policy_uri !== undefined && { policy_uri }),
+        ...(tos_uri !== undefined && { tos_uri }),
+      };
+    }
📝 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
if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
updates.metadata = {
...client.metadata,
contacts,
logo_uri,
client_uri,
policy_uri,
tos_uri,
};
}
if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
updates.metadata = {
...client.metadata,
...(contacts !== undefined ? { contacts } : {}),
...(logo_uri !== undefined ? { logo_uri } : {}),
...(client_uri !== undefined ? { client_uri } : {}),
...(policy_uri !== undefined ? { policy_uri } : {}),
...(tos_uri !== undefined ? { tos_uri } : {}),
};
}
🤖 Prompt for AI Agents
In src/controllers/oauthDynamicRegistrationController.ts around lines 435 to
444, the code unconditionally sets contacts, logo_uri, client_uri, policy_uri,
and tos_uri into updates.metadata which lets undefined values overwrite existing
client.metadata; change the logic to merge client.metadata with only the
explicitly provided fields by building a new metadata object that starts from
client.metadata and then conditionally assigns each property only if the
corresponding request field is !== undefined (or present), e.g. check each field
and add it to the metadata map before assigning updates.metadata, so existing
metadata keys are preserved when a field is omitted.

Comment thread src/controllers/oauthServerController.ts Outdated
Comment thread src/controllers/oauthServerController.ts
Ptah-CT pushed a commit that referenced this pull request Nov 27, 2025
…y innovations

Major upstream features added:
- OAuth 2.0 Authorization Server with RFC 7591
- Environment variable expansion
- User management with RBAC
- Registry integration
- Session persistence with SSE keepalive
- Enhanced smart routing
- Vector search improvements

Local XInfty features preserved:
- X^∞ - Quiet_Revolution documentation
- SERVER_STARTUP_DELAY environment variable
- copyServer() functionality
- Mistral API support

Build and core tests passing.
Co-authored-by: samanhappy <samanhappy@gmail.com>
Copy link
Copy Markdown

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/middlewares/auth.ts (1)

61-86: Based on my verification, I can now provide the rewritten review comment:

Bearer auth must set user context to avoid downstream failures.

The validateBearerAuth check (lines 61-64) runs before OAuth validation and successfully authenticates the request without setting req.user. However, the subsequent userContextMiddleware and downstream controllers (e.g., authController.ts line 180, userController.ts line 23) expect req.user to be defined, causing runtime errors like "Cannot read property 'username' of undefined".

For consistency with the OAuth (lines 78-82) and JWT (line 104) authentication paths, the bearer auth path should also set req.user with username and admin status. Additionally, when both bearer auth and OAuth are enabled, bearer auth takes precedence—verify this is intentional, as it may bypass OAuth user context enrichment.

src/controllers/serverController.ts (1)

552-916: Refactor: Extract functions from updateSystemConfig.

This function is 364 lines long with very high cyclomatic complexity, making it difficult to maintain, test, and understand. The function handles validation, initialization, updates, and persistence all in one place.

Consider extracting helper functions:

  1. Validation helpers:

    • validateRoutingUpdate(routing)
    • validateSmartRoutingUpdate(smartRouting)
    • validateOAuthServerUpdate(oauthServer)
  2. Initialization helpers:

    • initializeDefaultSystemConfig()
    • ensureDefaultSections(systemConfig)
  3. Update handlers:

    • applyRoutingUpdates(systemConfig, routing)
    • applySmartRoutingUpdates(systemConfig, smartRouting)
    • applyOAuthServerUpdates(systemConfig, oauthServer)
  4. Business logic:

    • shouldSyncEmbeddings(prev, current)

Example refactor structure:

export const updateSystemConfig = async (req: Request, res: Response): Promise<void> => {
  try {
    const updates = extractUpdates(req.body);
    
    if (!hasValidUpdates(updates)) {
      res.status(400).json({ success: false, message: 'Invalid system configuration' });
      return;
    }

    const systemConfig = await getOrInitializeSystemConfig();
    const needsSync = applyUpdates(systemConfig, updates);
    
    await saveSystemConfig(systemConfig);
    
    res.json({ success: true, data: systemConfig, message: 'System configuration updated' });
    
    if (needsSync) {
      syncAllServerToolsEmbeddings().catch(console.error);
    }
  } catch (error) {
    res.status(500).json({ success: false, message: 'Internal server error' });
  }
};

This would improve:

  • Testability (each function can be unit tested)
  • Readability (clear separation of concerns)
  • Maintainability (easier to modify individual sections)
  • Reusability (helpers can be used elsewhere)
🧹 Nitpick comments (28)
frontend/src/contexts/ServerContext.tsx (1)

283-314: Consider stronger typing for the return value.

The function returns a well-defined shape but the interface declares Promise<any> at line 30. For better type safety:

 interface ServerContextType {
   // ...
-  handleServerEdit: (server: Server) => Promise<any>;
+  handleServerEdit: (server: Server) => Promise<{
+    name: string;
+    status: string;
+    tools: any[];
+    config: Record<string, any>;
+  } | null>;
   // ...
 }
src/controllers/openApiController.ts (2)

114-122: Tool name resolution and schema-based arg coercion look correct

Building fullToolName with getNameSeparator() and preferring the matched tool’s name keeps this endpoint in sync with whatever naming convention the server actually uses, while still supporting both prefixed and unprefixed tool names. Grabbing inputSchema from the matched tool and running convertParametersToTypes over args is a clean way to normalize query/body values without breaking tools that lack a schema (they get the raw args back).

Only minor note: the inline comment on Line 133 still says “without server prefix as it gets added by handleCallToolRequest”, but toolName may now already include a prefix when taken from tool.name. Consider updating that comment to avoid future confusion; behavior itself looks fine.

Also applies to: 126-129, 131-136


211-225: Async group lookup fix is good; also return/await the server fallback

Awaiting getGroupByIdOrName(name) is the right fix and makes the existence check behave as intended.

However, when the group is missing you call getServerOpenAPISpec(req, res); and then return; without awaiting/returning the Promise. That means any async errors thrown inside getServerOpenAPISpec won’t be caught by this try/catch and may surface as unhandled rejections instead of a structured 5xx response.

You can fix this by returning the Promise directly:

-    const group = await getGroupByIdOrName(name);
-    if (!group) {
-      getServerOpenAPISpec(req, res);
-      return;
-    }
+    const group = await getGroupByIdOrName(name);
+    if (!group) {
+      return getServerOpenAPISpec(req, res);
+    }

This keeps behavior the same while propagating any errors through this controller’s async flow.

README.md (1)

248-254: Specify languages on new fenced code blocks to satisfy markdownlint (MD040)

The $smart/{group} HTTP and SSE examples use bare triple‑backtick fences without a language. To keep markdownlint happy and improve highlighting, consider marking them, e.g.:

```bash
# Search only within production servers
http://localhost:3000/mcp/$smart/production
...
# Search within a specific group
http://localhost:3000/sse/$smart/{group}
...

Also applies to: 299-305

README.zh.md (1)

177-183: Add languages to fenced code blocks for $smart/{group} examples (MD040)

The new 智能路由示例和 SSE 示例使用了不带语言标记的代码块(```)。为通过 markdownlint 并改善高亮,建议加上语言,例如:

```bash
# 在所有服务器中搜索
http://localhost:3000/mcp/$smart
# 在特定分组中搜索
http://localhost:3000/sse/$smart/{group}

Also applies to: 207-213, 259-264

src/middlewares/userContext.ts (2)

47-60: SSE user context cleanup is robust; consider reusing pattern for non‑SSE middleware

The cleanup + attachCleanupHandlers pattern for SSE/MCP endpoints (handling both finish and close with an idempotent guard) is a nice improvement and should prevent context from lingering on long‑lived or abruptly closed connections.

You may want to mirror this pattern in userContextMiddleware so aborted non‑SSE requests (where only close fires) also guarantee clearCurrentUser() is called.

Also applies to: 72-88


101-148: Clarify production intent of ContextAwareDataServiceImpl and avoid console logging of user data

ContextAwareDataServiceImpl.getUserDataFromContext currently returns hard‑coded “Admin level/User level data from context” blobs and logs dataType + user.username to console.log. That’s fine as an example or test shim, but if this is on a production path:

  • Replace the stubbed payloads with real data access or move this into a dedicated service.
  • Prefer a structured logger over console.log, and consider whether including usernames in logs fits your PII/logging policy.

If it is intended purely as an example/demo, adding a short comment to that effect would help future readers.

docs/zh/configuration/database-configuration.mdx (1)

213-229: Add languages to error snippets and verify docs URL consistency

  • Several fenced blocks showing error messages (e.g., “connect ECONNREFUSED…”, “password authentication failed…”, migration failures) use bare triple‑backticks. To satisfy markdownlint (MD040) and improve readability, consider marking them as text:

    ```text
    Error: connect ECONNREFUSED 127.0.0.1:5432
    
    
  • The support section links to https://mcphub.io/docs, whereas the main README points to https://docs.mcphubx.com/. It’s worth double‑checking which URL is canonical and aligning them to avoid confusing users.

Also applies to: 245-247, 260-262, 269-276, 326-328

README.fr.md (1)

60-89: DB mode description matches implementation; only minor French/style nits

The explanation of DB_URL auto‑enabling DB mode and USE_DB=false overriding it is consistent with the runtime logic (useDatabase in src/index.ts and initializeDaoFactory). From a behavior standpoint this section is accurate.

If you want to polish the French a bit later, you could:

  • Clarifier que USE_DB attend littéralement les valeurs true / false (chaîne) pour éviter toute ambiguïté.
  • Légèrement alléger les listes, par ex. « Configuration stockée dans une base de données professionnelle… » → « Configuration stockée dans une base de données robuste… ».

Nothing blocking here; this can ship as‑is.

src/db/entities/User.ts (1)

1-33: Entity structure looks good; consider hiding password by default

The entity layout (UUID id, unique username, timestamps) is consistent with the rest of the DB model.

For security, consider preventing the password hash from being selected in ordinary queries so it can’t accidentally leak through generic repository access or debug logs:

-  @Column({ type: 'varchar', length: 255 })
+  @Column({ type: 'varchar', length: 255, select: false })
   password: string;

Auth code that needs the hash can explicitly add the column in its queries. This is a low‑effort hardening that often pays off.

src/db/entities/Group.ts (1)

1-36: Group entity is fine; consider uniqueness on name and servers nullability

The entity matches the rest of the DB model and should work as-is. Two small modeling tweaks to consider:

  • If group names are expected to be unique (they’re used in path segments like /mcp/{group}), adding unique: true on name will let the DB enforce that invariant instead of relying solely on app logic.
  • servers is simple-json and non-nullable; if you allow empty groups, making it nullable or giving it a default [] can avoid insert quirks when servers is omitted.

Example:

-  @Column({ type: 'varchar', length: 255 })
+  @Column({ type: 'varchar', length: 255, unique: true })
   name: string;

-  @Column({ type: 'simple-json' })
-  servers: Array<string | { name: string; tools?: string[] | 'all' }>;
+  @Column({ type: 'simple-json', nullable: true })
+  servers?: Array<string | { name: string; tools?: string[] | 'all' }>;

Only adjust if this matches your intended constraints.

docker-compose.db.yml (2)

1-1: The version key is deprecated in Docker Compose V2.

Docker Compose V2 ignores this field. You can safely remove it, though it won't cause issues if left in place.

-version: "3.8"
-
 services:

28-30: Consider adding SSL/TLS for database connections in the documentation.

For production deployments, the DB_URL should include ?sslmode=require or ?sslmode=verify-full. The current configuration works for local development but the commented example could guide users toward secure defaults.

       # Database connection (setting DB_URL automatically enables database mode)
       DB_URL: "postgresql://mcphub:${DB_PASSWORD:-mcphub_password}@postgres:5432/mcphub"
+
+      # For production with SSL:
+      # DB_URL: "postgresql://mcphub:${DB_PASSWORD}@postgres:5432/mcphub?sslmode=require"
src/middlewares/auth.ts (2)

72-72: Redundant token comparison.

The getToken(accessToken) function already returns the token only when valid and not expired, so the additional oauthToken.accessToken === accessToken check is unnecessary.

-    if (oauthToken && oauthToken.accessToken === accessToken) {
+    if (oauthToken) {

74-75: Consider using a static import instead of dynamic import.

The dynamic import adds latency to every OAuth-authenticated request. Since findUserByUsername is likely always available, a static import at the top of the file would be more efficient.

Add to imports at the top:

import { findUserByUsername } from '../models/User.js';

Then simplify the usage:

-      const { findUserByUsername } = await import('../models/User.js');
-      const user = await findUserByUsername(oauthToken.username);
+      const user = await findUserByUsername(oauthToken.username);
docs/configuration/database-configuration.mdx (2)

80-81: Add health check condition to depends_on for consistency.

The example here differs from docker-compose.db.yml which uses condition: service_healthy. Without the health check, MCPHub may attempt to connect before PostgreSQL is ready.

     depends_on:
-      - postgres
+      postgres:
+        condition: service_healthy

Also add a healthcheck to the postgres service in this example for completeness.


113-117: Consider least-privilege database permissions.

GRANT ALL PRIVILEGES is convenient for development but overly permissive for production. Consider documenting more restrictive grants.

-- More restrictive alternative for production:
GRANT CONNECT ON DATABASE mcphub TO mcphub;
GRANT USAGE ON SCHEMA public TO mcphub;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mcphub;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO mcphub;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO mcphub;
src/db/repositories/index.ts (1)

1-6: Minor inconsistency in import style.

VectorEmbeddingRepository uses default import while others use named imports. Consider aligning for consistency, though this is purely stylistic.

src/dao/DatabaseDaoFactory.ts (2)

34-67: Simplify by removing non-null assertions.

The non-null assertions (!) after the null checks are safe but unnecessary. Assigning and returning directly within the if block is cleaner.

   getUserDao(): UserDao {
     if (!this.userDao) {
       this.userDao = new UserDaoDbImpl();
     }
-    return this.userDao!;
+    return this.userDao;
   }

Apply similarly to other getter methods. Since TypeScript's control flow analysis understands that this.userDao is definitely assigned after the if block, the assertion is redundant.


69-78: Consider adding a static reset for the singleton instance in tests.

The resetInstances() method clears cached DAOs but doesn't reset the singleton itself. For complete test isolation, you may also need to reset the static instance.

   public resetInstances(): void {
     this.userDao = null;
     this.serverDao = null;
     this.groupDao = null;
     this.systemConfigDao = null;
     this.userConfigDao = null;
   }
+
+  /**
+   * Reset singleton instance (for testing only)
+   */
+  public static resetInstance(): void {
+    DatabaseDaoFactory.instance = undefined as any;
+  }
src/dao/SystemConfigDaoDbImpl.ts (2)

15-55: Consider extracting the entity-to-domain mapping to reduce duplication.

The same mapping logic (lines 17-26, 31-40, 45-54) is repeated in get(), update(), and reset(). This violates DRY and increases maintenance burden.

+  private mapToSystemConfig(config: any): SystemConfig {
+    return {
+      routing: config.routing,
+      install: config.install,
+      smartRouting: config.smartRouting,
+      mcpRouter: config.mcpRouter,
+      nameSeparator: config.nameSeparator,
+      oauth: config.oauth,
+      oauthServer: config.oauthServer,
+      enableSessionRebuild: config.enableSessionRebuild,
+    };
+  }
+
   async get(): Promise<SystemConfig> {
     const config = await this.repository.get();
-    return {
-      routing: config.routing as any,
-      install: config.install as any,
-      smartRouting: config.smartRouting as any,
-      mcpRouter: config.mcpRouter as any,
-      nameSeparator: config.nameSeparator,
-      oauth: config.oauth as any,
-      oauthServer: config.oauthServer as any,
-      enableSessionRebuild: config.enableSessionRebuild,
-    };
+    return this.mapToSystemConfig(config);
   }

This also allows removing the as any casts if the types are properly aligned between the entity and domain model.


57-59: Return type inconsistency with interface contract.

The SystemConfigDao interface declares getSection to return Promise<SystemConfig[K] | undefined>, but the repository's getSection returns SystemConfig[K] (non-nullable per the snippet). The cast to any masks this, but the DAO should align with its interface contract by handling potential undefined values explicitly.

src/dao/GroupDaoDbImpl.ts (2)

54-69: Passing undefined fields may cause unintended behavior.

When entity.name, entity.description, etc. are undefined, they are explicitly set in the update payload. Depending on how repository.merge handles undefined, this could unintentionally clear existing values. Consider filtering out undefined keys:

   async update(id: string, entity: Partial<IGroup>): Promise<IGroup | null> {
-    const group = await this.repository.update(id, {
-      name: entity.name,
-      description: entity.description,
-      servers: entity.servers as any,
-      owner: entity.owner,
-    });
+    const updateData: Partial<IGroup> = {};
+    if (entity.name !== undefined) updateData.name = entity.name;
+    if (entity.description !== undefined) updateData.description = entity.description;
+    if (entity.servers !== undefined) updateData.servers = entity.servers as any;
+    if (entity.owner !== undefined) updateData.owner = entity.owner;
+    const group = await this.repository.update(id, updateData);

109-124: Direct entity mutation before persisting may cause issues.

Line 119 mutates group.servers directly on the fetched entity. While it works here, this pattern can cause subtle bugs with TypeORM's change tracking or if the entity is used elsewhere. Consider building a new array instead:

     if (!serverExists) {
-      group.servers.push(serverName);
-      await this.update(groupId, { servers: group.servers as any });
+      const updatedServers = [...group.servers, serverName];
+      await this.update(groupId, { servers: updatedServers as any });
     }
src/db/repositories/SystemConfigRepository.ts (1)

51-57: Non-atomic reset may leave inconsistent state.

The reset() method deletes then calls get() to recreate defaults. A concurrent request between these operations could observe a missing config or trigger duplicate creation attempts. If atomicity is important, wrap in a transaction.

src/dao/UserDaoDbImpl.ts (2)

52-59: Consider increasing bcrypt salt rounds.

The current implementation uses 10 salt rounds, which is the minimum recommended value. Modern security best practices (OWASP) suggest using 12-14 rounds for better protection against brute-force attacks.

Apply this diff to increase salt rounds to 12:

+const BCRYPT_SALT_ROUNDS = 12;
+
 async createWithHashedPassword(
   username: string,
   password: string,
   isAdmin: boolean,
 ): Promise<IUser> {
-  const hashedPassword = await bcrypt.hash(password, 10);
+  const hashedPassword = await bcrypt.hash(password, BCRYPT_SALT_ROUNDS);
   return await this.create({ username, password: hashedPassword, isAdmin });
 }

 async updatePassword(username: string, newPassword: string): Promise<boolean> {
-  const hashedPassword = await bcrypt.hash(newPassword, 10);
+  const hashedPassword = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
   const result = await this.update(username, { password: hashedPassword });
   return result !== null;
 }

Also applies to: 94-98


86-92: Consider mitigating timing attacks in credential validation.

The method returns false immediately when a user doesn't exist, but performs bcrypt.compare() when the user exists. This creates a timing difference that could theoretically be exploited to enumerate valid usernames. While bcrypt's inherent slowness (~100ms) makes this less practical, defense-in-depth suggests running a dummy comparison even for non-existent users.

Apply this diff to add timing attack mitigation:

 async validateCredentials(username: string, password: string): Promise<boolean> {
   const user = await this.findByUsername(username);
   if (!user) {
+    // Run a dummy hash comparison to prevent timing attacks
+    await bcrypt.compare(password, '$2b$10$AAAAAAAAAAAAAAAAAAAAAA');
     return false;
   }
   return await bcrypt.compare(password, user.password);
 }
src/db/entities/Server.ts (1)

29-57: Consider database-specific JSON types for better query support.

Multiple columns use simple-json type, which uses JSON.stringify/parse and doesn't support querying nested fields. If using PostgreSQL, consider using jsonb type for better performance and query capabilities. However, if SQLite compatibility is required, simple-json is appropriate.

For PostgreSQL deployments, consider:

-@Column({ type: 'simple-json', nullable: true })
+@Column({ type: 'jsonb', nullable: true })
 tools?: Record<string, { enabled: boolean; description?: string }>;

-@Column({ type: 'simple-json', nullable: true })
+@Column({ type: 'jsonb', nullable: true })
 prompts?: Record<string, { enabled: boolean; description?: string }>;

-@Column({ type: 'simple-json', nullable: true })
+@Column({ type: 'jsonb', nullable: true })
 options?: Record<string, any>;

Note: This would require conditional logic based on database type or separate entity definitions per database.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dac0d37 and 063b081.

📒 Files selected for processing (38)
  • .env.example (1 hunks)
  • README.fr.md (1 hunks)
  • README.md (6 hunks)
  • README.zh.md (5 hunks)
  • docker-compose.db.yml (1 hunks)
  • docs/configuration/database-configuration.mdx (1 hunks)
  • docs/docs.json (6 hunks)
  • docs/zh/configuration/database-configuration.mdx (1 hunks)
  • frontend/src/contexts/ServerContext.tsx (5 hunks)
  • src/config/DaoConfigService.ts (2 hunks)
  • src/controllers/authController.ts (5 hunks)
  • src/controllers/groupController.ts (23 hunks)
  • src/controllers/openApiController.ts (4 hunks)
  • src/controllers/serverController.ts (15 hunks)
  • src/controllers/userController.ts (13 hunks)
  • src/dao/DaoFactory.ts (1 hunks)
  • src/dao/DatabaseDaoFactory.ts (1 hunks)
  • src/dao/GroupDaoDbImpl.ts (1 hunks)
  • src/dao/ServerDaoDbImpl.ts (1 hunks)
  • src/dao/SystemConfigDaoDbImpl.ts (1 hunks)
  • src/dao/UserConfigDaoDbImpl.ts (1 hunks)
  • src/dao/UserDaoDbImpl.ts (1 hunks)
  • src/dao/index.ts (1 hunks)
  • src/db/entities/Group.ts (1 hunks)
  • src/db/entities/Server.ts (1 hunks)
  • src/db/entities/SystemConfig.ts (1 hunks)
  • src/db/entities/User.ts (1 hunks)
  • src/db/entities/UserConfig.ts (1 hunks)
  • src/db/entities/index.ts (1 hunks)
  • src/db/repositories/GroupRepository.ts (1 hunks)
  • src/db/repositories/ServerRepository.ts (1 hunks)
  • src/db/repositories/SystemConfigRepository.ts (1 hunks)
  • src/db/repositories/UserConfigRepository.ts (1 hunks)
  • src/db/repositories/UserRepository.ts (1 hunks)
  • src/db/repositories/index.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/middlewares/auth.ts (4 hunks)
  • src/middlewares/userContext.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .env.example
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/controllers/authController.ts
🧰 Additional context used
🧬 Code graph analysis (21)
src/db/entities/Group.ts (5)
src/db/entities/Server.ts (1)
  • Entity (12-64)
src/db/entities/SystemConfig.ts (1)
  • Entity (7-41)
src/db/entities/User.ts (1)
  • Entity (12-31)
src/db/entities/UserConfig.ts (1)
  • Entity (12-31)
src/db/entities/index.ts (1)
  • Group (12-12)
src/db/entities/User.ts (5)
src/db/entities/Group.ts (1)
  • Entity (12-34)
src/db/entities/Server.ts (1)
  • Entity (12-64)
src/db/entities/SystemConfig.ts (1)
  • Entity (7-41)
src/db/entities/UserConfig.ts (1)
  • Entity (12-31)
src/db/entities/index.ts (1)
  • User (12-12)
src/middlewares/auth.ts (3)
src/services/oauthServerService.ts (1)
  • isOAuthServerEnabled (331-333)
src/models/OAuth.ts (1)
  • getToken (222-243)
src/models/User.ts (1)
  • findUserByUsername (32-41)
src/config/DaoConfigService.ts (2)
src/dao/DaoFactory.ts (10)
  • getUserDao (44-49)
  • getUserDao (133-135)
  • getServerDao (51-56)
  • getServerDao (137-139)
  • getGroupDao (58-63)
  • getGroupDao (141-143)
  • getSystemConfigDao (65-70)
  • getSystemConfigDao (145-147)
  • getUserConfigDao (72-77)
  • getUserConfigDao (149-151)
src/dao/DatabaseDaoFactory.ts (5)
  • getUserDao (34-39)
  • getServerDao (41-46)
  • getGroupDao (48-53)
  • getSystemConfigDao (55-60)
  • getUserConfigDao (62-67)
src/dao/SystemConfigDaoDbImpl.ts (3)
src/dao/SystemConfigDao.ts (1)
  • SystemConfigDao (7-32)
src/db/repositories/SystemConfigRepository.ts (1)
  • SystemConfigRepository (9-76)
src/types/index.ts (1)
  • SystemConfig (152-176)
src/dao/UserConfigDaoDbImpl.ts (4)
src/dao/UserConfigDao.ts (1)
  • UserConfigDao (7-54)
src/db/repositories/UserConfigRepository.ts (1)
  • UserConfigRepository (8-82)
src/db/entities/index.ts (1)
  • UserConfig (12-12)
src/types/index.ts (1)
  • UserConfig (178-181)
src/controllers/openApiController.ts (3)
src/config/index.ts (1)
  • getNameSeparator (183-186)
src/utils/parameterConversion.ts (1)
  • convertParametersToTypes (14-93)
src/services/groupService.ts (1)
  • getGroupByIdOrName (28-43)
src/dao/GroupDaoDbImpl.ts (3)
src/dao/GroupDao.ts (1)
  • GroupDao (9-39)
src/db/repositories/GroupRepository.ts (1)
  • GroupRepository (8-93)
src/types/index.ts (1)
  • IGroup (16-22)
src/dao/DatabaseDaoFactory.ts (10)
src/dao/DaoFactory.ts (1)
  • DaoFactory (10-16)
src/dao/UserDao.ts (1)
  • UserDao (9-34)
src/dao/ServerDao.ts (1)
  • ServerDao (8-44)
src/dao/GroupDao.ts (1)
  • GroupDao (9-39)
src/dao/SystemConfigDao.ts (1)
  • SystemConfigDao (7-32)
src/dao/UserConfigDao.ts (1)
  • UserConfigDao (7-54)
src/dao/UserDaoDbImpl.ts (1)
  • UserDaoDbImpl (9-108)
src/dao/GroupDaoDbImpl.ts (1)
  • GroupDaoDbImpl (8-154)
src/dao/SystemConfigDaoDbImpl.ts (1)
  • SystemConfigDaoDbImpl (8-68)
src/dao/UserConfigDaoDbImpl.ts (1)
  • UserConfigDaoDbImpl (8-79)
src/controllers/userController.ts (4)
src/dao/DaoFactory.ts (2)
  • getSystemConfigDao (65-70)
  • getSystemConfigDao (145-147)
src/dao/DatabaseDaoFactory.ts (1)
  • getSystemConfigDao (55-60)
src/services/userService.ts (5)
  • getAllUsers (5-8)
  • getUserByUsername (11-15)
  • getAdminCount (109-113)
  • deleteUser (75-93)
  • getUserCount (103-106)
src/utils/passwordValidation.ts (1)
  • validatePasswordStrength (15-42)
src/db/entities/Server.ts (5)
src/db/entities/Group.ts (1)
  • Entity (12-34)
src/db/entities/SystemConfig.ts (1)
  • Entity (7-41)
src/db/entities/User.ts (1)
  • Entity (12-31)
src/db/entities/UserConfig.ts (1)
  • Entity (12-31)
src/db/entities/index.ts (1)
  • Server (12-12)
src/db/repositories/GroupRepository.ts (5)
src/db/repositories/index.ts (1)
  • GroupRepository (13-13)
src/db/entities/index.ts (1)
  • Group (12-12)
src/db/connection.ts (1)
  • getAppDataSource (67-69)
src/db/repositories/ServerRepository.ts (1)
  • count (68-70)
src/db/repositories/UserRepository.ts (1)
  • count (68-70)
src/middlewares/userContext.ts (1)
src/utils/oauthBearer.ts (1)
  • resolveOAuthUserFromAuthHeader (31-44)
src/index.ts (1)
src/utils/migration.ts (1)
  • initializeDatabaseMode (143-175)
src/db/entities/SystemConfig.ts (1)
src/types/index.ts (1)
  • SystemConfig (152-176)
src/dao/ServerDaoDbImpl.ts (2)
src/dao/ServerDao.ts (2)
  • ServerDao (8-44)
  • ServerConfigWithName (49-51)
src/db/repositories/ServerRepository.ts (1)
  • ServerRepository (8-92)
src/db/repositories/UserConfigRepository.ts (4)
src/db/repositories/index.ts (1)
  • UserConfigRepository (15-15)
src/db/entities/index.ts (1)
  • UserConfig (12-12)
src/types/index.ts (1)
  • UserConfig (178-181)
src/db/connection.ts (1)
  • getAppDataSource (67-69)
src/controllers/groupController.ts (1)
src/services/groupService.ts (11)
  • getAllGroups (20-25)
  • getGroupByIdOrName (28-43)
  • createGroup (46-84)
  • updateGroup (87-124)
  • updateGroupServers (128-160)
  • deleteGroup (163-171)
  • addServerToGroup (174-213)
  • removeServerFromGroup (216-236)
  • getServerConfigsInGroup (258-262)
  • getServerConfigInGroup (247-255)
  • updateServerToolsInGroup (265-306)
src/dao/UserDaoDbImpl.ts (3)
src/dao/UserDao.ts (1)
  • UserDao (9-34)
src/db/repositories/UserRepository.ts (1)
  • UserRepository (8-78)
src/types/index.ts (1)
  • IUser (9-13)
src/db/repositories/UserRepository.ts (8)
src/db/repositories/index.ts (1)
  • UserRepository (11-11)
src/db/entities/index.ts (1)
  • User (12-12)
src/db/connection.ts (1)
  • getAppDataSource (67-69)
src/dao/GroupDaoDbImpl.ts (1)
  • count (79-81)
src/dao/ServerDaoDbImpl.ts (1)
  • count (71-73)
src/dao/UserDaoDbImpl.ts (1)
  • count (82-84)
src/db/repositories/GroupRepository.ts (1)
  • count (83-85)
src/db/repositories/ServerRepository.ts (1)
  • count (68-70)
src/db/entities/index.ts (1)
src/types/index.ts (2)
  • SystemConfig (152-176)
  • UserConfig (178-181)
🪛 LanguageTool
docs/configuration/database-configuration.mdx

[style] ~196-~196: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...system/config` The web dashboard works exactly the same way, but now stores changes in the data...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)

README.zh.md

[uncategorized] ~205-~205: 您的意思是“由"于"”吗?
Context: ...PHub 设置中启用智能路由 分组限定的智能路由: 您可以将智能路由与分组筛选结合使用,仅在特定服务器分组内搜索: ``` # 仅在生产服务器中搜索...

(YU7_YU8)

README.fr.md

[typographical] ~62-~62: Caractère d’apostrophe incorrect.
Context: ...PostgreSQL comme alternative au fichier mcp_settings.json. Le mode base de données offre une pers...

(APOS_INCORRECT)


[style] ~62-~62: Un verbe peut dynamiser votre phrase.
Context: ...ance et une évolutivité améliorées pour les environnements de production et les déploiements d'entrep...

(PROP_NOMINALES_EN_VERBALES)


[grammar] ~64-~64: Il y a peut-être une erreur ici
Context: ...déploiements d'entreprise. Avantages principaux : - ✅ Meilleure persistance - Configurat...

(QB_NEW_FR)


[grammar] ~66-~66: Envisagez un remplacement
Context: ...paux :** - ✅ Meilleure persistance - Configuration stockée dans une base de données profes...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_PUNCTUATION_DASH_–)


[grammar] ~67-~67: Envisagez un remplacement
Context: ...des données - ✅ Haute disponibilité - Profitez des capacités de réplication et de basc...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_PUNCTUATION_DASH_–)


[grammar] ~68-~68: Envisagez un remplacement
Context: ... données - ✅ Prêt pour l'entreprise - Répond aux exigences de gestion des données et...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_PUNCTUATION_DASH_–)


[grammar] ~69-~69: Envisagez un remplacement
Context: ...rise - ✅ Sauvegarde et récupération - Utilisez des outils et stratégies de sauvegarde ...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_PUNCTUATION_DASH_–)


[style] ~69-~69: Cette structure peut être modifiée afin de devenir plus percutante.
Context: ... des outils et stratégies de sauvegarde de base de données matures **Variables d'envir...

(DE_BASE3)


[grammar] ~71-~71: Il y a peut-être une erreur ici
Context: ...de base de données matures Variables d'environnement : bash # Définissez simplement DB_URL pour activer automatiquement le mode base de données DB_URL=postgresql://user:password@host:5432/mcphub # Ou contrôlez explicitement avec USE_DB (optionnel, remplace la détection automatique) # USE_DB=true > Note : Vous n'avez qu'à définir `DB_UR...

(QB_NEW_FR)


[grammar] ~83-~83: Il y a peut-être une erreur ici
Context: ...ion/database-configuration.mdx) complet pour : - Instructions de configuration détaillées...

(QB_NEW_FR)


[style] ~86-~86: Utilisé hors du contexte militaire, on préfèrera d’autres tournures à « baser sur ».
Context: ...ées - Migration depuis la configuration basée sur fichiers - Procédures de sauvegarde et ...

(BASER_SUR)

🪛 markdownlint-cli2 (0.18.1)
README.zh.md

207-207: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

248-248: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Comment thread src/dao/DaoFactory.ts
Comment on lines +110 to +128
/**
* Switch to database-backed DAOs based on environment variable
* This is synchronous and should be called during app initialization
*/
export function initializeDaoFactory(): void {
// If USE_DB is explicitly set, use its value; otherwise, auto-detect based on DB_URL presence
const useDatabase =
process.env.USE_DB !== undefined ? process.env.USE_DB === 'true' : !!process.env.DB_URL;
if (useDatabase) {
console.log('Using database-backed DAO implementations');
// Dynamic import to avoid circular dependencies
// eslint-disable-next-line @typescript-eslint/no-var-requires
const DatabaseDaoFactoryModule = require('./DatabaseDaoFactory.js');
setDaoFactory(DatabaseDaoFactoryModule.DatabaseDaoFactory.getInstance());
} else {
console.log('Using file-based DAO implementations');
setDaoFactory(JsonFileDaoFactory.getInstance());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check package.json to understand module system configuration
cat package.json | head -30

Repository: XInfty/ct-mcphub

Length of output: 844


🏁 Script executed:

#!/bin/bash
# View the full DaoFactory.ts file to understand context and imports
wc -l src/dao/DaoFactory.ts

Repository: XInfty/ct-mcphub

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Check src/index.ts for duplicated logic
fd index.ts --type f src/

Repository: XInfty/ct-mcphub

Length of output: 270


🏁 Script executed:

#!/bin/bash
# Check tsconfig.json to understand compilation target
cat tsconfig.json

Repository: XInfty/ct-mcphub

Length of output: 623


🏁 Script executed:

#!/bin/bash
# Read the full DaoFactory.ts file to understand the context and usage pattern
cat -n src/dao/DaoFactory.ts

Repository: XInfty/ct-mcphub

Length of output: 4156


🏁 Script executed:

#!/bin/bash
# Check src/index.ts to see if there's duplicated useDatabase logic
cat -n src/index.ts

Repository: XInfty/ct-mcphub

Length of output: 525


🏁 Script executed:

#!/bin/bash
# Search for initializeDaoFactory function
rg -n "initializeDaoFactory" src/

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for useDatabase to find the logic mentioned in the review
rg -n "useDatabase" src/

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check the server.js file (imported in index.ts) to see if logic is there
fd server.ts --type f src/

Repository: XInfty/ct-mcphub

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Check git status and branch
git status

Repository: XInfty/ct-mcphub

Length of output: 295


🏁 Script executed:

#!/bin/bash
# Check recent commits to understand the state
git log --oneline -10

Repository: XInfty/ct-mcphub

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Look at lines 110-131 of DaoFactory.ts specifically (the end of the file)
sed -n '105,131p' src/dao/DaoFactory.ts | cat -n

Repository: XInfty/ct-mcphub

Length of output: 836


🏁 Script executed:

#!/bin/bash
# Check if there are any git diffs showing pending changes
git diff --stat HEAD

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for DatabaseDaoFactory to see if it exists
rg -n "DatabaseDaoFactory" src/

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for process.env.USE_DB or DB_URL to find initialization logic
rg -n "process\.env\.(USE_DB|DB_URL)" src/

Repository: XInfty/ct-mcphub

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Check server.ts for initialization logic
cat -n src/server.ts | head -50

Repository: XInfty/ct-mcphub

Length of output: 2087


🏁 Script executed:

#!/bin/bash
# Check the full git log for DaoFactory.ts to see if it was recently changed
git log --oneline src/dao/DaoFactory.ts | head -10

Repository: XInfty/ct-mcphub

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Check diff of DaoFactory.ts in the last commit
git show HEAD:src/dao/DaoFactory.ts | wc -l

Repository: XInfty/ct-mcphub

Length of output: 63


🏁 Script executed:

#!/bin/bash
# Search for any commented code or TODOs related to require or DatabaseDaoFactory
rg -n "(TODO|FIXME|require|DatabaseDaoFactory)" src/dao/DaoFactory.ts

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check if there are other branches or pending changes
git branch -a

Repository: XInfty/ct-mcphub

Length of output: 196


🏁 Script executed:

#!/bin/bash
# Check the coderabbit_1 branch to see proposed changes
git show coderabbit_1:src/dao/DaoFactory.ts 2>/dev/null | tail -40

Repository: XInfty/ct-mcphub

Length of output: 1388


🏁 Script executed:

#!/bin/bash
# Check if there's a diff between HEAD and coderabbit_1
git diff HEAD coderabbit_1 -- src/dao/DaoFactory.ts | head -100

Repository: XInfty/ct-mcphub

Length of output: 1258


🏁 Script executed:

#!/bin/bash
# Look at the context around lines 110-128 in coderabbit_1
git show coderabbit_1:src/dao/DaoFactory.ts 2>/dev/null | sed -n '105,135p' | cat -n

Repository: XInfty/ct-mcphub

Length of output: 1368


🏁 Script executed:

#!/bin/bash
# Check if DatabaseDaoFactory.ts exists
fd DatabaseDaoFactory.ts src/

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for any other DB_URL or USE_DB usage to check for duplication
rg -n "DB_URL|USE_DB" src/ --type ts

Repository: XInfty/ct-mcphub

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Check if initializeDaoFactory is called anywhere in the PR branch
git show coderabbit_1:src/index.ts 2>/dev/null | head -30

Repository: XInfty/ct-mcphub

Length of output: 958


🏁 Script executed:

#!/bin/bash
# Verify the tsconfig module compilation output by checking what the dist output would be
# Check if there are build artifacts or scripts that show compiled output format
cat tsconfig.json | rg "module|target"

Repository: XInfty/ct-mcphub

Length of output: 199


🏁 Script executed:

#!/bin/bash
# Search more broadly for any DatabaseDaoFactory file or reference
rg -i "databasedao" src/ --type ts

Repository: XInfty/ct-mcphub

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check if there's any TypeScript/ESM import() usage in the codebase as a pattern example
rg -n "import(" src/ --type ts | head -5

Repository: XInfty/ct-mcphub

Length of output: 124


Bare require will fail at runtime in ESM mode, and DB-mode detection logic is duplicated

Two critical issues in the proposed initializeDaoFactory function:

  1. Bare require incompatible with NodeNext module system

    With tsconfig.json configured as "module": "NodeNext", TypeScript compiles to ESM (import/export statements). A bare require is undefined at runtime and will throw ReferenceError: require is not defined when useDatabase is true. Additionally, DatabaseDaoFactory.ts does not exist in the codebase, so the require target cannot be resolved.

    Fix: Use dynamic import() and make initializeDaoFactory async:

    export async function initializeDaoFactory(): Promise<void> {
      // ... existing logic ...
      if (useDatabase) {
        const { DatabaseDaoFactory } = await import('./DatabaseDaoFactory.js');
        setDaoFactory(DatabaseDaoFactory.getInstance());
      }
    }
  2. Duplicated DB-mode detection logic

    The exact same useDatabase expression appears in both src/index.ts (lines 10-13) and the proposed code in src/dao/DaoFactory.ts (lines 12-13). This duplication creates maintenance risk—any future changes to the logic must be applied in two places or behavior will drift between initialization points.

    Fix: Extract the logic into a shared utility function (e.g., src/utils/databaseMode.ts):

    export function shouldUseDatabase(): boolean {
      return process.env.USE_DB !== undefined 
        ? process.env.USE_DB === 'true' 
        : !!process.env.DB_URL;
    }
🤖 Prompt for AI Agents
In src/dao/DaoFactory.ts around lines 110 to 128, the current
initializeDaoFactory uses a bare require (which fails under NodeNext/ESM) and
duplicates the DB-mode detection logic from src/index.ts; change
initializeDaoFactory to be async and use dynamic import() with the correct
runtime module path (ensure you import the actual DatabaseDaoFactory module that
exists, using the .js runtime extension), and move the USE_DB/DB_URL detection
into a shared utility (e.g., src/utils/databaseMode.ts with a
shouldUseDatabase() function) so both src/index.ts and DaoFactory call that
helper instead of duplicating the expression; update all call sites to await the
async initializeDaoFactory and to import the helper.

Comment thread src/dao/GroupDaoDbImpl.ts
Comment on lines +94 to +107
async findByServer(serverName: string): Promise<IGroup[]> {
const allGroups = await this.repository.findAll();
return allGroups
.filter((g) =>
g.servers.some((s) => (typeof s === 'string' ? s === serverName : s.name === serverName)),
)
.map((g) => ({
id: g.id,
name: g.name,
description: g.description,
servers: g.servers as any,
owner: g.owner,
}));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inefficient in-memory filtering; consider a database-level query.

findByServer loads all groups into memory and filters in JavaScript. This doesn't scale well as the number of groups grows. Consider adding a repository method that queries the database directly (e.g., using JSON operators if supported by your DB) or indexing server associations.

Comment thread src/dao/UserDaoDbImpl.ts
Comment on lines +39 to +50
async create(entity: Omit<IUser, 'id'>): Promise<IUser> {
const user = await this.repository.create({
username: entity.username,
password: entity.password,
isAdmin: entity.isAdmin || false,
});
return {
username: user.username,
password: user.password,
isAdmin: user.isAdmin,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Risk of storing unhashed passwords via create().

The create() method accepts a password field directly without hashing it. If a caller mistakenly uses create() instead of createWithHashedPassword(), plain-text passwords could be stored in the database.

Consider one of these solutions:

  1. Make create() private and force all creation through createWithHashedPassword()
  2. Add password hashing logic to create() as well
  3. Add explicit validation/warning if password doesn't look hashed

Apply this diff to add basic protection:

 async create(entity: Omit<IUser, 'id'>): Promise<IUser> {
+  // Warn if password doesn't look hashed (bcrypt hashes start with $2)
+  if (entity.password && !entity.password.startsWith('$2')) {
+    console.warn('Warning: Possibly unhashed password passed to UserDaoDbImpl.create()');
+  }
   const user = await this.repository.create({
     username: entity.username,
     password: entity.password,
     isAdmin: entity.isAdmin || false,
   });
📝 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
async create(entity: Omit<IUser, 'id'>): Promise<IUser> {
const user = await this.repository.create({
username: entity.username,
password: entity.password,
isAdmin: entity.isAdmin || false,
});
return {
username: user.username,
password: user.password,
isAdmin: user.isAdmin,
};
}
async create(entity: Omit<IUser, 'id'>): Promise<IUser> {
// Warn if password doesn't look hashed (bcrypt hashes start with $2)
if (entity.password && !entity.password.startsWith('$2')) {
console.warn('Warning: Possibly unhashed password passed to UserDaoDbImpl.create()');
}
const user = await this.repository.create({
username: entity.username,
password: entity.password,
isAdmin: entity.isAdmin || false,
});
return {
username: user.username,
password: user.password,
isAdmin: user.isAdmin,
};
}

Comment thread src/db/repositories/ServerRepository.ts
Comment on lines +20 to +40
async get(): Promise<SystemConfig> {
let config = await this.repository.findOne({ where: { id: this.DEFAULT_ID } });

// Create default if doesn't exist
if (!config) {
config = this.repository.create({
id: this.DEFAULT_ID,
routing: {},
install: {},
smartRouting: {},
mcpRouter: {},
nameSeparator: '-',
oauth: {},
oauthServer: {},
enableSessionRebuild: false,
});
config = await this.repository.save(config);
}

return config;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential race condition on first access.

If multiple concurrent requests call get() before the default config exists, the check-then-create pattern (lines 21-37) could result in duplicate insert attempts or constraint violations if id has a unique constraint. Consider using upsert or a transaction with locking for safer initialization.

   async get(): Promise<SystemConfig> {
-    let config = await this.repository.findOne({ where: { id: this.DEFAULT_ID } });
-
-    // Create default if doesn't exist
-    if (!config) {
-      config = this.repository.create({
-        id: this.DEFAULT_ID,
-        routing: {},
-        install: {},
-        smartRouting: {},
-        mcpRouter: {},
-        nameSeparator: '-',
-        oauth: {},
-        oauthServer: {},
-        enableSessionRebuild: false,
-      });
-      config = await this.repository.save(config);
-    }
-
-    return config;
+    let config = await this.repository.findOne({ where: { id: this.DEFAULT_ID } });
+    if (config) return config;
+
+    // Use upsert to handle race conditions
+    await this.repository.upsert({
+      id: this.DEFAULT_ID,
+      routing: {},
+      install: {},
+      smartRouting: {},
+      mcpRouter: {},
+      nameSeparator: '-',
+      oauth: {},
+      oauthServer: {},
+      enableSessionRebuild: false,
+    }, ['id']);
+    
+    return (await this.repository.findOne({ where: { id: this.DEFAULT_ID } }))!;
   }
🤖 Prompt for AI Agents
In src/db/repositories/SystemConfigRepository.ts around lines 20 to 40, the
current check-then-create flow can race when multiple callers hit get() before
the default row exists; replace it with an atomic upsert or perform the create
inside a transaction with a row-level lock to avoid duplicate inserts: either
use the ORM's upsert/insert...onConflict behavior (repository.upsert or query
builder insert with onConflict to do nothing and then select) so the call
becomes idempotent, or open a transaction (QueryRunner), SELECT the row FOR
UPDATE, create and save only if still missing, then commit; ensure the method
returns the existing or newly created config in all cases.

Comment on lines +37 to +54
async update(username: string, configData: Partial<UserConfig>): Promise<UserConfig> {
let config = await this.get(username);

if (!config) {
// Create new config if doesn't exist
config = this.repository.create({
username,
routing: {},
additionalConfig: {},
...configData,
});
} else {
// Merge with existing config
config = this.repository.merge(config, configData);
}

return await this.repository.save(config);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Race condition on concurrent config creation.

Similar to SystemConfigRepository, the check-then-create pattern in update() can cause duplicate entries or constraint violations if two requests simultaneously try to create a config for the same new user. Consider using upsert for atomicity.

🤖 Prompt for AI Agents
In src/db/repositories/UserConfigRepository.ts around lines 37 to 54, the
current check-then-create pattern can race when two requests create the same
user config; replace it with an atomic upsert: call the repository's upsert (or
use QueryBuilder INSERT ... ON CONFLICT) keyed on username, passing username
plus merged configData, then reload and return the saved entity; ensure the
upsert updates the routing/additionalConfig fields (merge existing and incoming
keys if needed) and use a follow-up find/get to return the persisted UserConfig.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
locales/fr.json (1)

734-734: Fix corrupted French text with mixed Chinese character.

Line 734 contains "和" (Chinese character meaning "and") mixed into French text. Should read "et" in French for consistency.

-      "groupIdAndServerNameRequired": "L'ID de groupe和le nom du serveur sont requis",
+      "groupIdAndServerNameRequired": "L'ID de groupe et le nom du serveur sont requis",
🧹 Nitpick comments (10)
src/db/entities/Server.ts (2)

29-60: Consider using native JSON columns for PostgreSQL.

The simple-json type uses JSON.stringify/JSON.parse which can be less efficient than native json or jsonb types in PostgreSQL. If targeting PostgreSQL, consider using the native types for better performance and query capabilities.

Example for PostgreSQL:

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   args?: string[];

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   env?: Record<string, string>;

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   headers?: Record<string, string>;

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   tools?: Record<string, { enabled: boolean; description?: string }>;

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   prompts?: Record<string, { enabled: boolean; description?: string }>;

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   options?: Record<string, any>;

-  @Column({ type: 'simple-json', nullable: true })
+  @Column({ type: 'jsonb', nullable: true })
   oauth?: Record<string, any>;

Note: If supporting multiple databases, simple-json provides better compatibility.


17-42: Consider adding indexes for frequently queried fields.

Fields like owner, enabled, and type are likely to be queried frequently. Adding indexes would improve query performance.

Add index decorators:

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
  Index,
} from 'typeorm';

Then add indexes to relevant columns:

+  @Index()
   @Column({ type: 'varchar', length: 50, nullable: true })
   type?: string;

+  @Index()
   @Column({ type: 'boolean', default: true })
   enabled: boolean;

+  @Index()
   @Column({ type: 'varchar', length: 255, nullable: true })
   owner?: string;
frontend/src/components/ServerForm.tsx (5)

5-11: Consider typing the payload parameter.

The onSubmit callback accepts any as the payload type. For better type safety, consider defining a specific payload interface that matches the structure being constructed in handleSubmit.


75-76: Redundant env var initialization.

The formData.env is initialized via getInitialServerEnvVars(initialData) at line 75, but then envVars state is initialized separately with the same logic at lines 145-148. The formData.env field appears unused since the form uses envVars state directly.

Consider removing env from formData initialization or using formData.env consistently:

     env: getInitialServerEnvVars(initialData),
+    headers: initialData && initialData.config && initialData.config.headers
+      ? Object.entries(initialData.config.headers).map(([key, value]) => ({ key, value }))
+      : [],
-    headers: [],

Then remove the separate envVars and headerVars states, using formData.env and formData.headers instead.

Also applies to: 145-149


607-616: Consider extracting a helper for OpenAPI field updates.

The pattern url: prev.openapi?.url || '' is repeated in every OpenAPI field onChange handler (~15 times). Consider extracting a helper function similar to handleOAuthChange:

+  const handleOpenAPIChange = <K extends keyof NonNullable<ServerFormData['openapi']>>(
+    field: K,
+    value: NonNullable<ServerFormData['openapi']>[K],
+  ) => {
+    setFormData((prev) => ({
+      ...prev,
+      openapi: {
+        ...prev.openapi,
+        [field]: value,
+      },
+    }));
+  };

Then use it in handlers: onChange={(e) => handleOpenAPIChange('securityType', e.target.value as any)}


1024-1101: Large commented-out code block.

This section contains ~80 lines of commented-out OAuth form fields. If these are planned for future implementation, consider:

  1. Removing for now and tracking in an issue
  2. Or uncommenting if they're ready for use

The handleSubmit logic already handles these fields, so they would work if uncommented.

Would you like me to open an issue to track the completion of these OAuth form fields?


1-1373: Component is functional but consider modularization for maintainability.

At ~1370 lines, this component handles significant complexity. The current structure works, but for long-term maintainability, consider extracting:

  • OpenAPIConfigSection component
  • OAuthConfigSection component
  • KeepAliveConfigSection component

This is optional and can be deferred.

src/dao/ServerDaoDbImpl.ts (3)

1-12: Instantiation approach is straightforward; consider DI later for testability

Directly constructing ServerRepository in the constructor is fine for now and keeps this implementation simple. If you find yourself needing more flexible testing or multiple data sources, you might later swap this to accept the repository (or a factory) via dependency injection, but that’s optional at this stage.


90-93: Consider a repository‑level findByType to avoid full‑table scans

findByType currently does:

const allServers = await this.repository.findAll();
return allServers.filter((s) => s.type === type).map((s) => this.mapToServerConfig(s));

This is fine for small datasets, but it pulls the entire table into memory and relies on JS filtering, which won’t benefit from a DB index on type.

If/when Server counts grow, consider adding a findByType method on ServerRepository that performs WHERE type = :type in the DB and then calling that here instead:

// in ServerRepository
async findByType(type: string): Promise<Server[]> {
  return this.repository.find({ where: { type } });
}

// here
async findByType(type: string): Promise<ServerConfigWithName[]> {
  const servers = await this.repository.findByType(type);
  return servers.map((s) => this.mapToServerConfig(s));
}

116-150: Type mapping is clear; add a small guard around type if you want stronger safety

The mapper cleanly isolates the DB/entity shape from ServerConfigWithName. Two minor points you might consider:

  1. Runtime guard for type instead of a blind assertion

Right now:

type: server.type as 'stdio' | 'sse' | 'streamable-http' | 'openapi' | undefined,

If the DB ever contains an unexpected string (e.g. a future enum value or bad migration), the type system will happily accept it even though it’s out of the declared union.

You can cheaply validate and coerce invalid values to undefined:

-    return {
-      name: server.name,
-      type: server.type as 'stdio' | 'sse' | 'streamable-http' | 'openapi' | undefined,
+    const allowedTypes = ['stdio', 'sse', 'streamable-http', 'openapi'] as const;
+    const type =
+      server.type && allowedTypes.includes(server.type as (typeof allowedTypes)[number])
+        ? (server.type as (typeof allowedTypes)[number])
+        : undefined;
+
+    return {
+      name: server.name,
+      type,
       url: server.url,
       // ...
  1. Optionally tighten types for options/oauth

options and oauth are currently Record<string, any>, which is fine but loses the stronger typing you likely have on ServerConfig. If you want the compiler to help more here, you could import and use the precise types from ServerConfig instead of any, but that’s a nice‑to‑have rather than a blocker.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 063b081 and 8770b9c.

📒 Files selected for processing (8)
  • frontend/src/components/ServerForm.tsx (23 hunks)
  • frontend/src/types/index.ts (5 hunks)
  • locales/en.json (14 hunks)
  • locales/fr.json (14 hunks)
  • locales/tr.json (1 hunks)
  • locales/zh.json (14 hunks)
  • src/dao/ServerDaoDbImpl.ts (1 hunks)
  • src/db/entities/Server.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/components/ServerForm.tsx (1)
frontend/src/types/index.ts (3)
  • Server (206-218)
  • EnvVar (235-238)
  • ServerFormData (241-296)
src/db/entities/Server.ts (6)
src/db/entities/Group.ts (1)
  • Entity (12-34)
src/db/entities/SystemConfig.ts (1)
  • Entity (7-41)
src/db/entities/User.ts (1)
  • Entity (12-31)
src/db/entities/UserConfig.ts (1)
  • Entity (12-31)
frontend/src/types/index.ts (1)
  • Server (206-218)
src/db/entities/index.ts (1)
  • Server (12-12)
src/dao/ServerDaoDbImpl.ts (2)
src/dao/ServerDao.ts (2)
  • ServerDao (8-44)
  • ServerConfigWithName (49-51)
src/db/repositories/ServerRepository.ts (1)
  • ServerRepository (8-92)
🪛 Checkov (3.2.334)
locales/tr.json

[medium] 567-568: Basic Auth Credentials

(CKV_SECRET_4)

🔇 Additional comments (21)
locales/tr.json (1)

1-795: Turkish localization complete and well-formed.

The Turkish translations comprehensively cover all UI namespaces including OAuth, registry, and session management features. Structure and key coverage align with other locale files.

locales/en.json (1)

1-795: English localization comprehensive and well-structured.

Additions cover OAuth authorization flows, dynamic client registration, JSON import, and registry features with clear user-facing text. All new keys properly integrated with existing namespaces.

locales/fr.json (1)

1-795: French localization structure sound; all sections present except for the line 734 defect.

Additions comprehensively cover OAuth, JSON import, and registry features with professional French translations. Fix the corrupted character on line 734 and this file will be production-ready.

locales/zh.json (1)

1-796: Chinese localization comprehensive and well-executed.

All new OAuth server, registry, and session management features properly translated with professional Chinese text. Structure aligns with other locale files and key coverage is complete.

src/db/entities/Server.ts (3)

1-7: LGTM!

Standard TypeORM imports are correctly specified.


69-69: LGTM!

Default export follows the pattern used in other entity files.


41-42: File does not exist in repository.

The review comment references src/db/entities/Server.ts, but this file does not exist in the repository. The src/db/entities/ directory only contains VectorEmbedding.ts and index.ts. The review cannot be verified against a non-existent file.

Likely an incorrect or invalid review comment.

frontend/src/types/index.ts (5)

1-2: LGTM!

The addition of 'oauth_required' to the ServerStatus union type is appropriate for indicating servers that require OAuth authorization flow.


117-162: LGTM!

The OAuth configuration structure is comprehensive and well-organized, supporting:

  • Client credentials (clientId, clientSecret)
  • Token management (accessToken, refreshToken, scopes)
  • RFC 7591 dynamic registration with metadata
  • PKCE authorization flow (pendingAuthorization with codeVerifier, state)
  • RFC 8707 resource parameter

214-217: LGTM!

The Server.oauth field appropriately captures runtime OAuth state (authorization URL and CSRF state) separate from the full configuration in ServerConfig.oauth.


255-268: LGTM!

The keepAlive and oauth structures in ServerFormData are appropriately designed for form handling. The scopes field being a string (vs. array in ServerConfig) is correct since it's parsed into an array during form submission.


371-514: LGTM!

The Registry types are well-structured and comprehensive, supporting:

  • Variable configuration with choices, defaults, and secret handling
  • Package definitions with environment variables, arguments, and transports
  • Server metadata with icons, repository info, and official status
  • Cursor-based pagination via RegistryMetadata.nextCursor
frontend/src/components/ServerForm.tsx (4)

225-236: LGTM!

The handleOptionsChange function correctly handles the three option fields with appropriate typing and immutable state updates.


374-384: Verify: OAuth config excluded from stdio servers.

The oauthConfig is included in the payload for SSE/streamable-http server types (line 378), but not for stdio servers. Ensure this is intentional, as the PR mentions "OAuth authentication for upstream MCP servers" which could potentially include stdio servers that spawn processes connecting to OAuth-protected endpoints.


317-326: LGTM!

The OpenAPI schema parsing is properly wrapped in try/catch with a descriptive error message. The conditional URL/schema handling based on input mode is correct.


1273-1351: LGTM!

The Keep-Alive section is well-implemented:

  • Properly gated to SSE/streamable-http server types only
  • Sensible interval constraints (5s-5min)
  • Good use of i18n fallbacks for new translation keys
  • Consistent UI pattern with other collapsible sections
src/dao/ServerDaoDbImpl.ts (5)

14-22: Read methods (findAll / findById) look good

The findAll/findById wrappers are thin and consistent, and centralizing the entity→DTO conversion through mapToServerConfig is a good pattern to keep the rest of the codebase insulated from the DB entity shape.


24-43: Create implementation and enabled defaulting are reasonable

Mapping from ServerConfigWithName into the repository payload is clear, and defaulting enabled to true when omitted is explicit and easy to reason about. Assuming that aligns with the existing in‑memory DAO semantics, this looks good.

If you haven’t already, it’s worth double‑checking that other ServerDao implementations also default enabled the same way so behavior stays consistent across storage backends.


68-88: Wrapper methods for delete/exists/count/owner/enabled are fine

These thin wrappers over ServerRepository (delete, exists, count, findByOwner, findEnabled) are straightforward and consistently reuse mapToServerConfig where needed. No issues here.


95-114: Boolean helpers (setEnabled / updateTools / updatePrompts) are consistent

The boolean return contracts based on server !== null / result !== null are clear and match the idea of “did we find and update the record?”. Once the core update method’s partial‑update semantics are made robust, these helpers will inherit the improved behavior without further changes.


45-66: File path and code pattern do not exist in the repository

The review references src/dao/ServerDaoDbImpl.ts (lines 45-66), but this file does not exist. The actual file is src/dao/ServerDao.ts. More importantly, the problematic code pattern shown in the review—an update() method that constructs a full object literal with enumerated fields like type: entity.type, url: entity.url, ...—does not exist in the codebase.

The actual update() method (lines 133–151) safely handles partial updates:

async update(
  name: string,
  updates: Partial<ServerConfigWithName>,
): Promise<ServerConfigWithName | null> {
  const servers = await this.getAll();
  const index = servers.findIndex((server) => server.name === name);

  if (index === -1) {
    return null;
  }

  // Don't allow name changes
  const { name: _, ...allowedUpdates } = updates;
  const updatedServer = this.updateEntity(servers[index], allowedUpdates);
  servers[index] = updatedServer;

  await this.saveAll(servers);
  return updatedServer;
}

The implementation uses destructuring to filter out the name field and then merges via the spread operator in updateEntity(). Additionally, this is a JSON file-based DAO, not a TypeORM database implementation, so TypeORM's merge() behavior is not relevant.

Comment thread frontend/src/components/ServerForm.tsx
Comment thread src/db/entities/Server.ts
Comment on lines +26 to +27
@Column({ type: 'varchar', length: 500, nullable: true })
command?: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider increasing the command length limit.

A 500-character limit for command may be insufficient for absolute paths in deeply nested directories or commands with many arguments. Consider increasing to 1000 or using TEXT type.

Apply this diff to increase the limit:

-  @Column({ type: 'varchar', length: 500, nullable: true })
+  @Column({ type: 'varchar', length: 1000, nullable: true })
   command?: string;

Or use TEXT type for unlimited length:

-  @Column({ type: 'varchar', length: 500, nullable: true })
+  @Column({ type: 'text', nullable: true })
   command?: string;
📝 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
@Column({ type: 'varchar', length: 500, nullable: true })
command?: string;
@Column({ type: 'varchar', length: 1000, nullable: true })
command?: string;
Suggested change
@Column({ type: 'varchar', length: 500, nullable: true })
command?: string;
@Column({ type: 'text', nullable: true })
command?: string;
🤖 Prompt for AI Agents
In src/db/entities/Server.ts around lines 26-27, the command column is currently
defined as varchar(500) which may be too small; update the Column decorator to
either increase the length to 1000 (e.g. { type: 'varchar', length: 1000,
nullable: true }) or change the column to TEXT (e.g. { type: 'text', nullable:
true }) depending on DB portability needs, then generate/run the appropriate
migration to apply the schema change.

Copy link
Copy Markdown

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8770b9c and 7b8d9a7.

📒 Files selected for processing (5)
  • .github/copilot-instructions.md (9 hunks)
  • AGENTS.md (1 hunks)
  • README.fr.md (4 hunks)
  • README.md (3 hunks)
  • README.zh.md (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🧰 Additional context used
🪛 LanguageTool
.github/copilot-instructions.md

[grammar] ~179-~179: Use a hyphen to join words.
Context: ...TypeScript strict*: Follow strict type checking rules - Import style: `impo...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~238-~238: The official name of this software platform is spelled with a capital “H”.
Context: ...ssues locally first. ### CI Pipeline (.github/workflows/ci.yml) - Runs on Node.js 20...

(GITHUB)


[style] ~257-~257: Consider using a different verb for a more formal wording.
Context: ... - Lint errors: Run pnpm lint and fix reported issues ### Development Issues...

(FIX_RESOLVE)

README.fr.md

[grammar] ~1-~1: Il y a peut-être une erreur ici
Context: # MCPHub : Le Hub Unifié pour les Serveur...

(QB_NEW_FR)


[grammar] ~1-~1: Il y a peut-être une erreur ici
Context: # MCPHub : Le Hub Unifié pour les Serveurs MCP ...

(QB_NEW_FR)


[grammar] ~1-~1: Il y a peut-être une erreur ici
Context: # MCPHub : Le Hub Unifié pour les Serveurs MCP [E...

(QB_NEW_FR)


[grammar] ~1-~1: «  le hub unifié  » semble plus probable dans ce contexte
Context: # MCPHub : Le Hub Unifié pour les Serveurs MCP [English](README...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_UPPERCASE)


[grammar] ~1-~1: «  serveurs  » semble plus probable dans ce contexte
Context: # MCPHub : Le Hub Unifié pour les Serveurs MCP English | Français | [中文版](R...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_UPPERCASE)


[typographical] ~3-~3: Pas de correspondance fermante ou ouvrante pour le caractère « [ »
Context: ...b Unifié pour les Serveurs MCP English | Français | [中文版](README.zh...

(UNPAIRED_BRACKETS)


[typographical] ~3-~3: Pas de correspondance fermante ou ouvrante pour le caractère « ] »
Context: ... MCP English | Français | 中文版 MCPHub facilite la g...

(UNPAIRED_BRACKETS)


[style] ~19-~19: Un autre mot peut sembler plus précis et percutant.
Context: ...ation à chaud** - Ajoutez, supprimez ou mettez à jour les serveurs sans temps d'arrêt - **Sup...

(METTRE_A_JOUR)


[style] ~21-~21: Un verbe peut dynamiser votre phrase.
Context: ...z la configuration dans PostgreSQL pour les environnements de production ([En savoir plus](https://do...

(PROP_NOMINALES_EN_VERBALES)


[grammar] ~28-~28: Il y a peut-être une erreur ici
Context: ...de ### Configuration Créez un fichier mcp_settings.json : json { "mcpServers": { "time": { "command": "npx", "args": ["-y", "time-mcp"] }, "fetch": { "command": "uvx", "args": ["mcp-server-fetch"] } } } 📖 Consultez le [Guide de configuration]...

(QB_NEW_FR)


[style] ~45-~45: Une autre structure peut sembler plus percutante.
Context: ...cp-settings) pour les options complètes incluant OAuth, les variables d'environnement, e...

(INCLUANT_EXCLUANT)


[grammar] ~61-~61: Il y a peut-être une erreur ici
Context: ...admin / admin123 ### Connecter les clients IA Connectez les clients IA (Claude Desktop...

(QB_NEW_FR)


[grammar] ~63-~63: Il y a peut-être une erreur ici
Context: ...ients IA (Claude Desktop, Cursor, etc.) via : http://localhost:3000/mcp # Tous les serveurs http://localhost:3000/mcp/{group} # Groupe spécifique http://localhost:3000/mcp/{server} # Serveur spécifique http://localhost:3000/mcp/$smart # Routage intelligent 📖 Consultez la [Référence API](https://...

(QB_NEW_FR)


[grammar] ~74-~74: Il y a peut-être une erreur ici
Context: ...étaillée des points de terminaison. ## 📚 Documentation | Sujet ...

(QB_NEW_FR)


[typographical] ~94-~94: Caractère d’apostrophe incorrect.
Context: ... le backend et le frontend séparément : pnpm backend:dev, pnpm frontend:dev 📖 Consultez le [...

(APOS_INCORRECT)

🪛 markdownlint-cli2 (0.18.1)
README.fr.md

65-65: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

65-65: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (8)
README.zh.md (3)

16-22: Well-structured feature highlights with comprehensive reference links.

The condensed feature list is clear and directly links to relevant documentation for deeper dives. The new emphasis on Smart Routing, OAuth 2.0, and Database Mode aligns perfectly with PR objectives.


28-45: Clear quick-start configuration and helpful documentation links.

The simplified mcp_settings.json example is user-friendly, and the link to the Configuration Guide provides an easy path for users to learn more options (OAuth, environment variables, etc.).


59-72: Excellent endpoint examples and dashboard access instructions.

The unified endpoint paths (/mcp, /mcp/{group}, /mcp/{server}, /mcp/$smart) clearly communicate the routing flexibility, and the API Reference link supports discoverability.

.github/copilot-instructions.md (2)

140-150: Excellent addition of DAO Layer documentation.

Clear explanation of dual data source support (JSON file vs. PostgreSQL) and the checklist reference to AGENTS.md makes it straightforward for contributors to understand what files need updates when modifying data structures. This significantly improves the contributor experience.


179-179: Grammar suggestion flagged by static analysis is a false positive.

The LanguageTool suggestion about hyphenating "TypeScript strict" is incorrect in this context—this is not a compound adjective but rather a markdown formatting artifact. The current formatting is appropriate for the documentation list.

README.md (1)

16-22: Feature highlights clearly communicate the key value propositions.

Each feature includes a direct link to detailed documentation, making it easy for users to learn more about OAuth 2.0, smart routing, and database mode.

README.fr.md (2)

1-3: French README title and language links properly formatted.

The title simplification and consistent language navigation across all three versions (EN/FR/ZH) improve discoverability and user experience.


16-22: French feature descriptions maintain clarity and include documentation links.

The translation is accurate and the feature highlights directly reference the relevant documentation sections.

Comment thread README.fr.md
Comment thread README.md
Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
docs/configuration/database-configuration.mdx (2)

28-29: Clarify USE_DB documentation to reduce confusion.

The documentation states that setting DB_URL automatically enables database mode (lines 28-29) and USE_DB is optional. However, the docker-compose example on line 70 explicitly sets USE_DB: "true", which may confuse users into thinking it's required. Either:

  1. Remove the redundant USE_DB: "true" from the example and rely on auto-detection, or
  2. Add a comment explaining that it's explicit for clarity despite being redundant

For consistency, consider clarifying line 142's reference to "when you start MCPHub with USE_DB=true" to reflect that DB_URL alone is sufficient.

Also applies to: 70-71


179-179: Minor wordiness improvement.

Line 179 uses "works exactly the same way" which is slightly wordy. Consider: "The web dashboard works the same way" or "functions identically" for conciseness. This aligns with documentation style best practices.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8d9a7 and 884870c.

📒 Files selected for processing (6)
  • docs/configuration/database-configuration.mdx (1 hunks)
  • docs/zh/configuration/database-configuration.mdx (1 hunks)
  • docs/zh/development.mdx (1 hunks)
  • docs/zh/essentials/code.mdx (12 hunks)
  • docs/zh/essentials/markdown.mdx (2 hunks)
  • docs/zh/essentials/navigation.mdx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/zh/essentials/navigation.mdx
  • docs/zh/configuration/database-configuration.mdx
  • docs/zh/essentials/code.mdx
🧰 Additional context used
🪛 LanguageTool
docs/configuration/database-configuration.mdx

[style] ~179-~179: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...system/config` The web dashboard works exactly the same way, but now stores changes in the data...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)

🔇 Additional comments (4)
docs/zh/essentials/markdown.mdx (1)

136-136: Clarify documentation scope for endpoint examples.

The code examples now use http://localhost:3000, which is appropriate for local development but may confuse users if they copy-paste these examples from published documentation. This raises a question about the intended audience and use case.

Recommendation: Either:

  1. Update examples to use a production or placeholder endpoint (e.g., https://api.example.com or ${MCPHUB_ENDPOINT})
  2. Add a contextual note indicating these are development-only examples, or
  3. Move locale-specific examples to a separate development setup guide

Please clarify:

  • Is this documentation intended for end-users or internal developers?
  • Should the examples demonstrate production setup or local development setup?
  • Are corresponding English examples in docs/essentials/markdown.mdx updated consistently?

Also applies to: 145-145

docs/zh/development.mdx (2)

51-51: Verify the repository URL change is intentional.

Line 51 changes the clone URL from https://github.com/mcphub/mcphub.git to https://github.com/samanhappy/mcphub.git. This appears to reflect a repository ownership or branding change. Confirm this is the intended primary repository URL for users.


119-126: Environment configuration appropriately simplified.

The removal of database-related environment variables and database initialization steps from the development guide is well-aligned with the PR's documentation restructuring. Database configuration is now centralized in docs/configuration/database-configuration.mdx, keeping this development guide focused on basic setup for local development.

docs/configuration/database-configuration.mdx (1)

1-298: Comprehensive and well-structured database configuration guide.

This new documentation file effectively addresses production database setup requirements. Strengths include:

  • Clear rationale for database mode adoption (lines 7-13)
  • Multiple setup paths (Docker external, Docker compose, manual)
  • Comprehensive migration guidance with both automatic and manual options (lines 136-169)
  • Practical troubleshooting section (lines 224-266)
  • Security considerations explicitly called out (lines 278-287)
  • Performance comparison to inform user decisions (lines 289-298)
  • Complete schema reference (lines 181-190)

The documentation provides enterprise-grade clarity and will serve users well for production deployments.

samanhappy and others added 30 commits May 18, 2026 15:01
…new features and improve clarity (#814)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…824)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…lling (#816)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…te limits (#829)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
…onfiguration (#826)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
#860)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…872)

Co-authored-by: Clement Patout <3038285+clempat@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: samanhappy <samanhappy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.