Skip to content

feat: added built-in tools with proper permissions #1420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,8 @@ ${params.message}`,
const typedParams = params as McpServerParams

if (params.id === 'add-new-mcp') {
//turning off splash loader in case of being on when new server is added
mynahUi.toggleSplashLoader(false)
const detailedList = createAddMcpServerDetailedList(typedParams)

const events = {
Expand All @@ -1438,15 +1440,14 @@ ${params.message}`,
} else if (actionParams.id === 'save-mcp') {
mynahUi.toggleSplashLoader(true, '**Activating MCP Server**')
messager.onMcpServerClick(actionParams.id, 'Save configuration', filterValues)
setTimeout(() => {
mynahUi.toggleSplashLoader(false)
}, 10000)
}
},
}

mynahUi.openDetailedList({ detailedList, events }, true)
} else if (params.id === 'open-mcp-server') {
//turning off splash loader in case of being on when new server is added
mynahUi.toggleSplashLoader(false)
const detailedList = createViewMcpServerDetailedList(typedParams)

const mcpServerSheet = mynahUi.openDetailedList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,11 @@ export class AgenticChatController implements ChatHandlers {
const { Tool } = toolMap[toolUse.name as keyof typeof toolMap]
const tool = new Tool(this.#features)

// For MCP tools, get the permission from McpManager
// const permission = McpManager.instance.getToolPerm('Built-in', toolUse.name)
// If permission is 'alwaysAllow', we don't need to ask for acceptance
// const builtInPermission = permission !== 'alwaysAllow'

// Get the approved paths from the session
const approvedPaths = session.approvedPaths

Expand All @@ -969,6 +974,9 @@ export class AgenticChatController implements ChatHandlers {
approvedPaths
)

// Honor built-in permission if available, otherwise use tool's requiresAcceptance
// const requiresAcceptance = builtInPermission || toolRequiresAcceptance

if (requiresAcceptance || toolUse.name === 'executeBash') {
// for executeBash, we till send the confirmation message without action buttons
const confirmationResult = this.#processToolConfirmation(
Expand Down Expand Up @@ -1549,7 +1557,8 @@ export class AgenticChatController implements ChatHandlers {
requiresAcceptance: Boolean,
warning?: string,
commandCategory?: CommandCategory,
toolType?: string
toolType?: string,
builtInPermission?: boolean
): ChatResult {
let buttons: Button[] = []
let header: {
Expand Down Expand Up @@ -1624,11 +1633,15 @@ export class AgenticChatController implements ChatHandlers {
header = {
icon: 'warning',
iconForegroundStatus: 'warning',
body: '#### Allow file modification outside of your workspace',
body: builtInPermission
? '#### Allow file modification'
: '#### Allow file modification outside of your workspace',
buttons,
}
const writeFilePath = (toolUse.input as unknown as FsWriteParams).path
body = `I need permission to modify files in your workspace.\n\`${writeFilePath}\``
body = builtInPermission
? `I need permission to modify files.\n\`${writeFilePath}\``
: `I need permission to modify files in your workspace.\n\`${writeFilePath}\``
break

case 'fsRead':
Expand All @@ -1644,18 +1657,24 @@ export class AgenticChatController implements ChatHandlers {
header = {
icon: 'tools',
iconForegroundStatus: 'tools',
body: '#### Allow read-only tools outside your workspace',
body: builtInPermission
? '#### Allow read-only tools'
: '#### Allow read-only tools outside your workspace',
buttons,
}
// ⚠️ Warning: This accesses files outside the workspace
if (toolUse.name === 'fsRead') {
const paths = (toolUse.input as unknown as FsReadParams).paths
const formattedPaths: string[] = []
paths.forEach(element => formattedPaths.push(`\`${element}\``))
body = `I need permission to read files outside the workspace.\n${formattedPaths.join('\n')}`
body = builtInPermission
? `I need permission to read files.\n${formattedPaths.join('\n')}`
: `I need permission to read files outside the workspace.\n${formattedPaths.join('\n')}`
} else {
const readFilePath = (toolUse.input as unknown as ListDirectoryParams).path
body = `I need permission to list directories outside the workspace.\n\`${readFilePath}\``
body = builtInPermission
? `I need permission to list directories.\n\`${readFilePath}\``
: `I need permission to list directories outside the workspace.\n\`${readFilePath}\``
}
break
// — DEFAULT ⇒ MCP tools
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Features } from '../../../types'
import { MCP_SERVER_STATUS_CHANGED, McpManager } from './mcpManager'
import { McpManager } from './mcpManager'
import {
DetailedListGroup,
DetailedListItem,
Expand All @@ -8,7 +8,7 @@ import {
McpServerClickParams,
} from '@aws/language-server-runtimes/protocol'

import { getGlobalMcpConfigPath, getGlobalPersonaConfigPath, getWorkspacePersonaConfigPaths } from './mcpUtils'
import { getGlobalMcpConfigPath, getGlobalPersonaConfigPath } from './mcpUtils'
import { MCPServerConfig, MCPServerPermission } from './mcpTypes'

interface PermissionOption {
Expand All @@ -33,6 +33,38 @@ export class McpEventHandler {
// Transform server configs into DetailedListItem objects
const activeItems: DetailedListItem[] = []
const disabledItems: DetailedListItem[] = []
const builtInItems: DetailedListItem[] = []

// Get built-in tools programmatically
const allTools = this.#features.agent.getTools({ format: 'bedrock' })
const mcpToolNames = new Set(mcpManager.getAllTools().map(tool => tool.toolName))
const builtInTools = allTools
.filter(tool => !mcpToolNames.has(tool.toolSpecification.name))
.map(tool => ({
name: tool.toolSpecification.name,
description: tool.toolSpecification.description || `${tool.toolSpecification.name} tool`,
}))

// Add built-in tools as a server in the active items
// activeItems.push({
// title: 'Built-in',
// description: `${builtInTools.length} tools`,
// children: [
// {
// groupName: 'serverInformation',
// children: [
// {
// title: 'status',
// description: 'ENABLED',
// },
// {
// title: 'toolcount',
// description: `${builtInTools.length}`,
// },
// ],
// },
// ],
// })

Array.from(mcpManagerServerConfigs.entries()).forEach(([serverName, config]) => {
const toolsWithPermissions = mcpManager.getAllToolsWithPermissions(serverName)
Expand Down Expand Up @@ -387,30 +419,67 @@ export class McpEventHandler {
return { id: params.id }
}

const toolsWithPermissions = McpManager.instance.getAllToolsWithPermissions(serverName)
const filterOptions = this.#buildServerFilterOptions(serverName, toolsWithPermissions)
let filterOptions: FilterOption[] = []

return {
id: params.id,
header: {
title: serverName,
status: {},
actions: [
{
id: 'edit-setup',
icon: 'pencil',
text: 'Edit setup',
},
{
id: 'mcp-details-menu',
icon: 'ellipsis-h',
text: '',
},
],
},
list: [],
filterActions: [],
filterOptions,
if (serverName === 'Built-in') {
// Handle Built-in server specially
const allTools = this.#features.agent.getTools({ format: 'bedrock' })
const mcpToolNames = new Set(McpManager.instance.getAllTools().map(tool => tool.toolName))
const builtInTools = allTools
.filter(tool => !mcpToolNames.has(tool.toolSpecification.name))
.map(tool => {
// Set default permission based on tool name
const permission = 'alwaysAllow'

return {
tool: {
toolName: tool.toolSpecification.name,
description: tool.toolSpecification.description || `${tool.toolSpecification.name} tool`,
},
permission,
}
})

filterOptions = this.#buildServerFilterOptions(serverName, builtInTools)

return {
id: params.id,
header: {
title: serverName,
status: {},
actions: [],
},
list: [],
filterActions: [],
filterOptions,
}
} else {
// Handle regular MCP servers
const toolsWithPermissions = McpManager.instance.getAllToolsWithPermissions(serverName)
filterOptions = this.#buildServerFilterOptions(serverName, toolsWithPermissions)

return {
id: params.id,
header: {
title: serverName,
status: {},
actions: [
{
id: 'edit-setup',
icon: 'pencil',
text: 'Edit setup',
},
{
id: 'mcp-details-menu',
icon: 'ellipsis-h',
text: '',
},
],
},
list: [],
filterActions: [],
filterOptions,
}
}
}

Expand Down Expand Up @@ -507,14 +576,16 @@ export class McpEventHandler {
value: 'workspace',
},
],
placeholder: 'global',
},
]

// Add tool select options
toolsWithPermissions.forEach(item => {
const toolName = item.tool.toolName
const currentPermission = this.#getCurrentPermission(item.permission)
const permissionOptions = this.#buildPermissionOptions(currentPermission)
// For Built-in server, use a special function that doesn't include the 'Disable' option
const permissionOptions = this.#buildPermissionOptions(item.permission)

filterOptions.push({
type: 'select',
Expand Down Expand Up @@ -571,6 +642,29 @@ export class McpEventHandler {
return permissionOptions
}

/**
* Builds permission options for Built-in tools (no 'Disable' option)
*/
// #buildBuiltInPermissionOptions(currentPermission: string) {
// const permissionOptions: PermissionOption[] = []

// if (currentPermission !== 'alwaysAllow') {
// permissionOptions.push({
// label: 'Always run',
// value: 'alwaysAllow',
// })
// }

// if (currentPermission !== 'ask') {
// permissionOptions.push({
// label: 'Ask to run',
// value: 'ask',
// })
// }

// return permissionOptions
// }

/**
* Handles MCP permission change events
*/
Expand All @@ -583,12 +677,15 @@ export class McpEventHandler {
}

try {
const serverConfig = McpManager.instance.getAllServerConfigs().get(serverName)
if (!serverConfig) {
throw new Error(`Server '${serverName}' not found`)
// Skip server config check for Built-in server
if (serverName !== 'Built-in') {
const serverConfig = McpManager.instance.getAllServerConfigs().get(serverName)
if (!serverConfig) {
throw new Error(`Server '${serverName}' not found`)
}
}

const MCPServerPermission = this.#processPermissionUpdates(updatedPermissionConfig)
const MCPServerPermission = this.#processPermissionUpdates(serverName, updatedPermissionConfig)

await McpManager.instance.updateServerPermission(serverName, MCPServerPermission)
return { id: params.id }
Expand Down Expand Up @@ -618,7 +715,7 @@ export class McpEventHandler {
/**
* Processes permission updates from the UI
*/
#processPermissionUpdates(updatedPermissionConfig: any) {
#processPermissionUpdates(serverName: string, updatedPermissionConfig: any) {
// TODO: handle ws/global selection
let personaPath = getGlobalPersonaConfigPath(this.#features.workspace.fs.getUserHomeDir())

Expand All @@ -631,6 +728,15 @@ export class McpEventHandler {
// Process each tool permission setting
for (const [key, val] of Object.entries(updatedPermissionConfig)) {
if (key === 'scope') continue

// // Get the default permission for this tool from McpManager
// let defaultPermission = McpManager.instance.getToolPerm(serverName, key)

// // If no default permission is found, use 'alwaysAllow' for Built-in and 'ask' for MCP servers
// if (!defaultPermission) {
// defaultPermission = serverName === 'Built-in' ? 'alwaysAllow' : 'ask'
// }

switch (val) {
case 'alwaysAllow':
perm.toolPerms[key] = 'alwaysAllow'
Expand Down