Skip to content

Commit 384180e

Browse files
author
Evyatar Mitrani
committed
mcp restrictions per mode
1 parent 0504041 commit 384180e

File tree

20 files changed

+1802
-18
lines changed

20 files changed

+1802
-18
lines changed

packages/types/src/mode.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,50 @@ export const groupEntrySchema = z.union([toolGroupsSchema, z.tuple([toolGroupsSc
3838

3939
export type GroupEntry = z.infer<typeof groupEntrySchema>
4040

41+
/**
42+
* McpRestrictions
43+
*/
44+
45+
export const mcpToolRestrictionSchema = z.object({
46+
serverName: z.string(),
47+
toolName: z.string(),
48+
})
49+
50+
export type McpToolRestriction = z.infer<typeof mcpToolRestrictionSchema>
51+
52+
export const mcpRestrictionsSchema = z
53+
.object({
54+
allowedServers: z.array(z.string()).optional(),
55+
disallowedServers: z.array(z.string()).optional(),
56+
allowedTools: z.array(mcpToolRestrictionSchema).optional(),
57+
disallowedTools: z.array(mcpToolRestrictionSchema).optional(),
58+
})
59+
.refine(
60+
(data) => {
61+
// Cannot have both allowedServers and disallowedServers
62+
if (data.allowedServers && data.disallowedServers) {
63+
return false
64+
}
65+
// Cannot have both allowedTools and disallowedTools for same server/tool combination
66+
if (data.allowedTools && data.disallowedTools) {
67+
// Filter out empty entries before checking for conflicts
68+
const allowedFiltered = data.allowedTools.filter((t) => t.serverName.trim() && t.toolName.trim())
69+
const disallowedFiltered = data.disallowedTools.filter((t) => t.serverName.trim() && t.toolName.trim())
70+
71+
const allowedSet = new Set(allowedFiltered.map((t) => `${t.serverName}:${t.toolName}`))
72+
const disallowedSet = new Set(disallowedFiltered.map((t) => `${t.serverName}:${t.toolName}`))
73+
const intersection = Array.from(allowedSet).filter((x) => disallowedSet.has(x))
74+
return intersection.length === 0
75+
}
76+
return true
77+
},
78+
{
79+
message: "Cannot have conflicting allowed/disallowed server or tool restrictions",
80+
},
81+
)
82+
83+
export type McpRestrictions = z.infer<typeof mcpRestrictionsSchema>
84+
4185
/**
4286
* ModeConfig
4387
*/
@@ -70,6 +114,7 @@ export const modeConfigSchema = z.object({
70114
customInstructions: z.string().optional(),
71115
groups: groupEntryArraySchema,
72116
source: z.enum(["global", "project"]).optional(),
117+
mcpRestrictions: mcpRestrictionsSchema.optional(),
73118
})
74119

75120
export type ModeConfig = z.infer<typeof modeConfigSchema>

src/core/prompts/sections/mcp-servers.ts

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,121 @@
11
import { DiffStrategy } from "../../../shared/tools"
22
import { McpHub } from "../../../services/mcp/McpHub"
3+
import { ModeConfig } from "@roo-code/types"
4+
import { getModeBySlug } from "../../../shared/modes"
5+
6+
// Helper functions for MCP restriction checking (copied from use-mcp-tool.ts)
7+
function isServerAllowedForMode(serverName: string, restrictions: any, serverDefaultEnabled?: boolean): boolean {
8+
// Handle defaultEnabled logic first
9+
// If server has defaultEnabled: false, it must be explicitly allowed
10+
if (serverDefaultEnabled === false) {
11+
// Only allowed if explicitly in allowedServers list
12+
return restrictions.allowedServers ? restrictions.allowedServers.includes(serverName) : false
13+
}
14+
15+
// For defaultEnabled: true (default behavior)
16+
// If allowedServers is defined, server must be in the list
17+
if (restrictions.allowedServers && !restrictions.allowedServers.includes(serverName)) {
18+
return false
19+
}
20+
21+
// If disallowedServers is defined, server must not be in the list
22+
if (restrictions.disallowedServers && restrictions.disallowedServers.includes(serverName)) {
23+
return false
24+
}
25+
26+
return true
27+
}
28+
29+
function isToolAllowedForModeAndServer(serverName: string, toolName: string, restrictions: any): boolean {
30+
// If allowedTools is defined, tool must be in the list
31+
if (restrictions.allowedTools) {
32+
// Filter out empty entries before checking
33+
const validAllowedTools = restrictions.allowedTools.filter(
34+
(t: any) => t.serverName?.trim() && t.toolName?.trim(),
35+
)
36+
if (validAllowedTools.length > 0) {
37+
const isAllowed = validAllowedTools.some((t: any) => t.serverName === serverName && t.toolName === toolName)
38+
if (!isAllowed) return false
39+
}
40+
}
41+
42+
// If disallowedTools is defined, tool must not be in the list
43+
if (restrictions.disallowedTools) {
44+
// Filter out empty entries before checking
45+
const validDisallowedTools = restrictions.disallowedTools.filter(
46+
(t: any) => t.serverName?.trim() && t.toolName?.trim(),
47+
)
48+
const isDisallowed = validDisallowedTools.some(
49+
(t: any) => t.serverName === serverName && t.toolName === toolName,
50+
)
51+
if (isDisallowed) return false
52+
}
53+
54+
return true
55+
}
356

457
export async function getMcpServersSection(
558
mcpHub?: McpHub,
659
diffStrategy?: DiffStrategy,
760
enableMcpServerCreation?: boolean,
61+
currentMode?: string,
62+
customModes?: ModeConfig[],
863
): Promise<string> {
964
if (!mcpHub) {
1065
return ""
1166
}
1267

68+
let availableServers = mcpHub.getServers()
69+
70+
// Filter servers based on mode restrictions
71+
if (currentMode && customModes) {
72+
const mode = getModeBySlug(currentMode, customModes)
73+
const restrictions = mode?.mcpRestrictions
74+
75+
if (restrictions || mode) {
76+
// Always filter based on defaultEnabled, even if no explicit restrictions
77+
availableServers = availableServers.filter((server) => {
78+
// Get server configuration to check defaultEnabled setting
79+
const serverConfig = mcpHub.getServerConfig(server.name)
80+
const defaultEnabled = serverConfig?.defaultEnabled ?? true // Default to true if not specified
81+
82+
return isServerAllowedForMode(server.name, restrictions || {}, defaultEnabled)
83+
})
84+
}
85+
}
86+
1387
const connectedServers =
14-
mcpHub.getServers().length > 0
15-
? `${mcpHub
16-
.getServers()
88+
availableServers.length > 0
89+
? `${availableServers
1790
.filter((server) => server.status === "connected")
1891
.map((server) => {
19-
const tools = server.tools
20-
?.filter((tool) => tool.enabledForPrompt !== false)
21-
?.map((tool) => {
22-
const schemaStr = tool.inputSchema
23-
? ` Input Schema:
92+
let availableTools = server.tools?.filter((tool) => tool.enabledForPrompt !== false) || []
93+
94+
// Filter tools based on mode restrictions
95+
if (currentMode && customModes) {
96+
const mode = getModeBySlug(currentMode, customModes)
97+
const restrictions = mode?.mcpRestrictions
98+
99+
if (restrictions) {
100+
availableTools = availableTools.filter((tool) =>
101+
isToolAllowedForModeAndServer(server.name, tool.name, restrictions),
102+
)
103+
}
104+
}
105+
106+
const tools =
107+
availableTools.length > 0
108+
? availableTools
109+
.map((tool) => {
110+
const schemaStr = tool.inputSchema
111+
? ` Input Schema:
24112
${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}`
25-
: ""
113+
: ""
26114
27-
return `- ${tool.name}: ${tool.description}\n${schemaStr}`
28-
})
29-
.join("\n\n")
115+
return `- ${tool.name}: ${tool.description}\n${schemaStr}`
116+
})
117+
.join("\n\n")
118+
: null
30119
31120
const templates = server.resourceTemplates
32121
?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`)
@@ -47,7 +136,9 @@ export async function getMcpServersSection(
47136
)
48137
})
49138
.join("\n\n")}`
50-
: "(No MCP servers currently connected)"
139+
: currentMode && customModes && getModeBySlug(currentMode, customModes)?.mcpRestrictions
140+
? "(No MCP servers are available for the current mode due to restrictions)"
141+
: "(No MCP servers currently connected)"
51142

52143
const baseSection = `MCP SERVERS
53144

src/core/prompts/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ async function generatePrompt(
5959
const [modesSection, mcpServersSection] = await Promise.all([
6060
getModesSection(context),
6161
modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "mcp")
62-
? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation)
62+
? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation, mode, customModeConfigs)
6363
: Promise.resolve(""),
6464
])
6565

src/core/prompts/tools/access-mcp-resource.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
11
import { ToolArgs } from "./types"
2+
import { getModeBySlug, isServerAllowedForMode } from "../../../shared/modes"
23

34
export function getAccessMcpResourceDescription(args: ToolArgs): string | undefined {
45
if (!args.mcpHub) {
56
return undefined
67
}
8+
9+
let availableServers = args.mcpHub.getServers()
10+
11+
// Filter servers based on mode restrictions
12+
if (args.currentMode && args.customModes) {
13+
const mode = getModeBySlug(args.currentMode, args.customModes)
14+
const restrictions = mode?.mcpRestrictions || {} // Use empty object if no restrictions defined
15+
16+
// Always filter based on defaultEnabled, even if no explicit restrictions
17+
availableServers = availableServers.filter((server) => {
18+
// Get server configuration to check defaultEnabled setting
19+
const serverConfig = args.mcpHub?.getServerConfig(server.name)
20+
const defaultEnabled = serverConfig?.defaultEnabled ?? true // Default to true if not specified
21+
22+
return isServerAllowedForMode(server.name, restrictions, defaultEnabled)
23+
})
24+
}
25+
26+
// Generate description with filtered servers and their available resources
27+
if (availableServers.length === 0) {
28+
return `## access_mcp_resource
29+
Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.
30+
**Note: No MCP servers are available for the current mode.**
31+
32+
This tool allows you to access resources provided by Model Context Protocol (MCP) servers, but the current mode has restrictions that prevent access to all configured MCP servers.
33+
34+
Parameters:
35+
- server_name: (required) The name of the MCP server providing the resource
36+
- uri: (required) The URI identifying the specific resource to access`
37+
}
38+
39+
const serverDescriptions = availableServers
40+
.map((server) => {
41+
const resourceCount = (server.resources || []).length
42+
const resourceTemplateCount = (server.resourceTemplates || []).length
43+
const totalResources = resourceCount + resourceTemplateCount
44+
45+
return `**${server.name}**: ${totalResources} resource${totalResources !== 1 ? "s" : ""} available`
46+
})
47+
.join("\n")
48+
749
return `## access_mcp_resource
850
Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.
51+
52+
**Available servers for current mode:**
53+
${serverDescriptions}
54+
955
Parameters:
1056
- server_name: (required) The name of the MCP server providing the resource
1157
- uri: (required) The URI identifying the specific resource to access
58+
1259
Usage:
1360
<access_mcp_resource>
1461
<server_name>server name here</server_name>

src/core/prompts/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export function getToolDescriptionsForMode(
7070
partialReadsEnabled,
7171
settings,
7272
experiments,
73+
currentMode: mode, // Pass current mode for MCP restriction checking
74+
customModes, // Pass custom modes for restriction lookup
7375
}
7476

7577
const tools = new Set<string>()

src/core/prompts/tools/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DiffStrategy } from "../../../shared/tools"
22
import { McpHub } from "../../../services/mcp/McpHub"
3+
import type { ModeConfig } from "@roo-code/types"
34

45
export type ToolArgs = {
56
cwd: string
@@ -11,4 +12,6 @@ export type ToolArgs = {
1112
partialReadsEnabled?: boolean
1213
settings?: Record<string, any>
1314
experiments?: Record<string, boolean>
15+
currentMode?: string // NEW: Current mode for restriction checking
16+
customModes?: ModeConfig[] // NEW: Custom modes for restriction lookup
1417
}

src/core/prompts/tools/use-mcp-tool.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,83 @@
11
import { ToolArgs } from "./types"
2+
import { getModeBySlug, isServerAllowedForMode, isToolAllowedForModeAndServer } from "../../../shared/modes"
23

34
export function getUseMcpToolDescription(args: ToolArgs): string | undefined {
45
if (!args.mcpHub) {
56
return undefined
67
}
8+
9+
let availableServers = args.mcpHub.getServers()
10+
11+
// NEW: Filter servers based on mode restrictions
12+
if (args.currentMode && args.customModes) {
13+
const mode = getModeBySlug(args.currentMode, args.customModes)
14+
const restrictions = mode?.mcpRestrictions || {} // Use empty object if no restrictions defined
15+
16+
// Always filter based on defaultEnabled, even if no explicit restrictions
17+
availableServers = availableServers.filter((server) => {
18+
// Get server configuration to check defaultEnabled setting
19+
const serverConfig = args.mcpHub?.getServerConfig(server.name)
20+
const defaultEnabled = serverConfig?.defaultEnabled ?? true // Default to true if not specified
21+
22+
return isServerAllowedForMode(server.name, restrictions, defaultEnabled)
23+
})
24+
}
25+
26+
// Generate description with filtered servers and their available tools
27+
if (availableServers.length === 0) {
28+
return `## use_mcp_tool
29+
Description: Request to use a tool provided by a connected MCP server.
30+
**Note: No MCP servers are available for the current mode.**
31+
32+
This tool allows you to execute tools provided by Model Context Protocol (MCP) servers, but the current mode has restrictions that prevent access to all configured MCP servers.
33+
34+
Parameters:
35+
- server_name: (required) The name of the MCP server providing the tool
36+
- tool_name: (required) The name of the tool to execute
37+
- arguments: (required) A JSON object containing the tool's input parameters`
38+
}
39+
40+
const serverDescriptions = availableServers
41+
.map((server) => {
42+
let availableTools = server.tools || []
43+
44+
// Filter tools based on mode restrictions (only if explicit restrictions exist)
45+
if (args.currentMode && args.customModes) {
46+
const mode = getModeBySlug(args.currentMode, args.customModes)
47+
const restrictions = mode?.mcpRestrictions
48+
49+
if (restrictions) {
50+
availableTools = availableTools.filter((tool) =>
51+
isToolAllowedForModeAndServer(server.name, tool.name, restrictions),
52+
)
53+
}
54+
}
55+
56+
const toolList =
57+
availableTools.length > 0
58+
? availableTools
59+
.map((tool) => {
60+
const desc = tool.description ? ` - ${tool.description}` : ""
61+
return ` • ${tool.name}${desc}`
62+
})
63+
.join("\n")
64+
: " (No tools available for current mode)"
65+
66+
return `**${server.name}**:\n${toolList}`
67+
})
68+
.join("\n\n")
69+
770
return `## use_mcp_tool
871
Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.
72+
73+
**Available servers and tools for current mode:**
74+
${serverDescriptions}
75+
976
Parameters:
1077
- server_name: (required) The name of the MCP server providing the tool
1178
- tool_name: (required) The name of the tool to execute
1279
- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
80+
1381
Usage:
1482
<use_mcp_tool>
1583
<server_name>server name here</server_name>

0 commit comments

Comments
 (0)