[codex] add MCP tools visibility management (#1170)
* feat(mcp): add tools visibility management ## Features - Tools visibility modal with 3 modes: All, Include, Exclude - 'Manage Tools' button on McpServerCard (enabled only when connected) - 'Fetch Tools List' button to refresh available tools (raw mode) - Responsive design for mobile (480px), tablet (768px), desktop (1280px) - i18n translations for 9 languages (zh/en/zh-TW/ja/ko/de/es/fr/pt) ## Technical Details - Add raw parameter to fetchMcpTools API for unfiltered tools - Pass raw parameter through controller → bridgeMcpAction → client - Backend _mcp_tools_list supports raw_mode to skip include/exclude filter - 28 MCP unit tests pass (23 controller + 5 bridge action) ## Files Changed - McpManagerView.vue: Tools visibility modal with mode selector - McpServerCard.vue: Add manage tools button - mcp.ts (client): Add raw parameter to fetchMcpTools - mcp.ts (controller): Pass raw parameter to bridge - mcp.ts (services): Pass raw parameter to client.mcpTools - client.ts: Add raw parameter to mcpTools - hermes_bridge.py: Support raw_mode in _mcp_tools_list - 9 locale files: Add 14 translation keys each - mcp-controller.test.ts: Add 3 new test cases - bridge-mcp-action.test.ts: New test file for parameter passing * Delete projects directory chore: remove accidentally committed projects/ directory * fix MCP tools visibility edge cases * remove MCP docs screenshots --------- Co-authored-by: Crafter-feng <succeed_happu@163.com> Co-authored-by: Crafter-feng <37255449+Crafter-feng@users.noreply.github.com>
This commit is contained in:
@@ -97,7 +97,10 @@ export async function testServer(ctx: Context) {
|
||||
export async function listTools(ctx: Context) {
|
||||
try {
|
||||
const server = ctx.query.server as string | undefined
|
||||
const payload = server ? { server } : {}
|
||||
const raw = ctx.query.raw === '1' || ctx.query.raw === 'true'
|
||||
const payload: Record<string, any> = {}
|
||||
if (server) payload.server = server
|
||||
if (raw) payload.raw = true
|
||||
ctx.body = await bridgeMcpAction('mcp_tools_list', payload, getProfile(ctx))
|
||||
} catch (err: any) {
|
||||
ctx.status = 503
|
||||
|
||||
@@ -609,8 +609,8 @@ export class AgentBridgeClient {
|
||||
return this.request({ action: 'mcp_server_test', name, ...(profile ? { profile } : {}) }, { timeoutMs: 180_000 })
|
||||
}
|
||||
|
||||
mcpTools(server?: string, profile?: string): Promise<McpActionResponse> {
|
||||
return this.request({ action: 'mcp_tools_list', ...(server ? { server } : {}), ...(profile ? { profile } : {}) })
|
||||
mcpTools(server?: string, profile?: string, raw?: boolean): Promise<McpActionResponse> {
|
||||
return this.request({ action: 'mcp_tools_list', ...(server ? { server } : {}), ...(profile ? { profile } : {}), ...(raw ? { raw } : {}) })
|
||||
}
|
||||
|
||||
mcpReload(server?: string, profile?: string): Promise<McpActionResponse> {
|
||||
|
||||
@@ -2427,16 +2427,18 @@ class BridgeServer:
|
||||
cfg = getattr(task, "_config", {})
|
||||
# Build filtered tool_details (name + description) for card display
|
||||
srv_cfg = mcp_configs.get(name, {}) if isinstance(mcp_configs.get(name), dict) else {}
|
||||
tools_filter = srv_cfg.get("tools") or {}
|
||||
tools_filter = srv_cfg.get("tools") if isinstance(srv_cfg.get("tools"), dict) else {}
|
||||
has_include_filter = "include" in tools_filter
|
||||
has_exclude_filter = "exclude" in tools_filter
|
||||
include_set = set(tools_filter.get("include") or [])
|
||||
exclude_set = set(tools_filter.get("exclude") or [])
|
||||
tool_details = []
|
||||
try:
|
||||
for mcp_tool in getattr(task, "_tools", []):
|
||||
tname = getattr(mcp_tool, "name", "?")
|
||||
if include_set and tname not in include_set:
|
||||
if has_include_filter and tname not in include_set:
|
||||
continue
|
||||
if exclude_set and tname in exclude_set:
|
||||
if has_exclude_filter and tname in exclude_set:
|
||||
continue
|
||||
tool_details.append({
|
||||
"name": tname,
|
||||
@@ -2576,6 +2578,7 @@ class BridgeServer:
|
||||
|
||||
def _mcp_tools_list(self, req: dict, profile: str, _servers, _lock) -> dict[str, Any]:
|
||||
server_filter = str(req.get("server") or "").strip() or None
|
||||
raw_mode = bool(req.get("raw")) # Return unfiltered tools for visibility management
|
||||
results = []
|
||||
|
||||
config = self._read_mcp_config(profile)
|
||||
@@ -2592,13 +2595,17 @@ class BridgeServer:
|
||||
registered = set(getattr(task, "_registered_tool_names", None) or [])
|
||||
tools = []
|
||||
srv_cfg = mcp_configs.get(sname, {}) if isinstance(mcp_configs.get(sname), dict) else {}
|
||||
tools_filter = srv_cfg.get("tools") or {}
|
||||
tools_filter = srv_cfg.get("tools") if isinstance(srv_cfg.get("tools"), dict) else {}
|
||||
has_include_filter = "include" in tools_filter
|
||||
has_exclude_filter = "exclude" in tools_filter
|
||||
include_set = set(tools_filter.get("include") or [])
|
||||
exclude_set = set(tools_filter.get("exclude") or [])
|
||||
def _should_include(tn):
|
||||
if include_set:
|
||||
if raw_mode:
|
||||
return True # Skip filter in raw mode
|
||||
if has_include_filter:
|
||||
return tn in include_set
|
||||
if exclude_set:
|
||||
if has_exclude_filter:
|
||||
return tn not in exclude_set
|
||||
return True
|
||||
try:
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function bridgeMcpAction(
|
||||
break
|
||||
}
|
||||
case 'mcp_tools_list':
|
||||
raw = await client.mcpTools(payload.server as string | undefined, profile)
|
||||
raw = await client.mcpTools(payload.server as string | undefined, profile, payload.raw as boolean | undefined)
|
||||
break
|
||||
case 'mcp_reload':
|
||||
raw = await client.mcpReload(payload.server as string | undefined, profile)
|
||||
|
||||
Reference in New Issue
Block a user