feat: check pre-configured MCP server urls and do not parse unless on the allowed list (#8055)

* feat: add ability for enterprise to disable user from adding MCP servers via remote config

* use a proper type check instead of an as any assertion

* feat: check remote mcp server url against user configured mcp server and do not parse servers not on the allow list if no personal server allowed

* feat: Enforce remote config's local MCP market place settings and filter by allowlist and source (#8068)

* feat: add parse/load enforcement for local mcp market place servers

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
This commit is contained in:
Zhongying Qiao
2025-12-16 00:04:46 -08:00
committed by GitHub
parent a20685d289
commit 3409fa7442
3 changed files with 49 additions and 0 deletions
+3
View File
@@ -24,6 +24,9 @@ export function transformRemoteConfigToStateShape(remoteConfig: RemoteConfig): P
if (remoteConfig.blockPersonalRemoteMCPServers !== undefined) {
transformed.blockPersonalRemoteMCPServers = remoteConfig.blockPersonalRemoteMCPServers
}
if (remoteConfig.remoteMCPServers !== undefined) {
transformed.remoteMCPServers = remoteConfig.remoteMCPServers
}
if (remoteConfig.yoloModeAllowed !== undefined) {
// only set the yoloModeToggled field if yolo mode is not allowed. Otherwise, we let the user toggle it.
if (remoteConfig.yoloModeAllowed === false) {
+45
View File
@@ -1,6 +1,7 @@
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
import { sendMcpServersUpdate } from "@core/controller/mcp/subscribeToMcpServers"
import { GlobalFileNames } from "@core/storage/disk"
import { StateManager } from "@core/storage/StateManager"
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
@@ -223,6 +224,50 @@ export class McpHub {
// Remove existing connection if it exists (should never happen, the connection should be deleted beforehand)
this.connections = this.connections.filter((conn) => conn.server.name !== name)
// Validate remote MCP server URL against remote config if blockPersonalRemoteMCPServers is enabled
if (config.type !== "stdio" && "url" in config && config.url) {
const stateManager = StateManager.get()
const remoteConfig = stateManager.getRemoteConfigSettings()
if (remoteConfig.blockPersonalRemoteMCPServers === true) {
const remoteMCPServers = remoteConfig.remoteMCPServers || []
const allowedUrls = remoteMCPServers.map((server) => server.url)
if (!allowedUrls.includes(config.url)) {
return
}
}
}
// Validate local MCP servers based on remote config
if (config.type === "stdio") {
const stateManager = StateManager.get()
const remoteConfig = stateManager.getRemoteConfigSettings()
// If marketplace is disabled, block all local servers
if (remoteConfig.mcpMarketplaceEnabled === false) {
return
}
// Check if server is from GitHub marketplace
if (name.startsWith("github.com/")) {
// If allowlist is configured, validate against it
if (remoteConfig.allowedMCPServers && remoteConfig.allowedMCPServers.length > 0) {
const allowedIds = remoteConfig.allowedMCPServers.map((server: { id: string }) => server.id)
if (!allowedIds.includes(name)) {
return
}
} else {
// If no allowlist, GitHub servers are not allowed
return
}
} else {
// Non-GitHub local servers are blocked
return
}
}
if (config.disabled) {
//console.log(`[MCP Debug] Creating disabled connection object for server "${name}"`)
// Create a connection object for disabled server so it appears in UI
+1
View File
@@ -27,6 +27,7 @@ export type GlobalStateAndSettings = GlobalState & Settings
export interface RemoteConfigExtraFields {
remoteConfiguredProviders: string[]
allowedMCPServers: Array<{ id: string }>
remoteMCPServers?: Array<{ name: string; url: string }>
remoteGlobalRules?: GlobalInstructionsFile[]
remoteGlobalWorkflows?: GlobalInstructionsFile[]
blockPersonalRemoteMCPServers?: boolean