c998a53566
* 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>
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { execFileSync } from 'child_process'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
function runPython(script: string): any {
|
|
try {
|
|
const output = execFileSync('python3', ['-c', script], {
|
|
cwd: process.cwd(),
|
|
encoding: 'utf-8',
|
|
stdio: 'pipe',
|
|
})
|
|
return JSON.parse(output)
|
|
} catch (error) {
|
|
const err = error as { stdout?: string; stderr?: string; message?: string }
|
|
throw new Error([
|
|
err.message || 'Python bridge MCP filter script failed',
|
|
err.stdout ? `stdout:\n${err.stdout}` : '',
|
|
err.stderr ? `stderr:\n${err.stderr}` : '',
|
|
].filter(Boolean).join('\n\n'))
|
|
}
|
|
}
|
|
|
|
describe('agent bridge MCP tools filtering', () => {
|
|
it('treats an empty include list as an active filter and keeps raw listing unfiltered', () => {
|
|
const result = runPython(String.raw`
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
path = Path("packages/server/src/services/hermes/agent-bridge/hermes_bridge.py")
|
|
spec = importlib.util.spec_from_file_location("hermes_bridge", path)
|
|
bridge = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = bridge
|
|
spec.loader.exec_module(bridge)
|
|
|
|
class Tool:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.description = f"{name} description"
|
|
self.inputSchema = {"type": "object"}
|
|
|
|
class Task:
|
|
_task = None
|
|
_error = None
|
|
|
|
def __init__(self):
|
|
self._tools = [Tool("read_file"), Tool("write_file"), Tool("delete_file")]
|
|
self._registered_tool_names = ["read_file", "write_file", "delete_file"]
|
|
self._config = {"command": "mcp-server"}
|
|
|
|
server = bridge.BridgeServer("tcp://127.0.0.1:0")
|
|
servers = {"fs": Task()}
|
|
lock = threading.RLock()
|
|
|
|
def names(response):
|
|
return [tool["name"] for tool in response["results"][0]["tools"]]
|
|
|
|
server._read_mcp_config = lambda profile: {
|
|
"mcp_servers": {
|
|
"fs": {
|
|
"command": "mcp-server",
|
|
"tools": {"include": []},
|
|
},
|
|
},
|
|
}
|
|
include_empty = server._mcp_tools_list({"server": "fs"}, "default", servers, lock)
|
|
include_empty_list = server._mcp_list("default", servers, lock)
|
|
include_empty_raw = server._mcp_tools_list({"server": "fs", "raw": True}, "default", servers, lock)
|
|
|
|
server._read_mcp_config = lambda profile: {
|
|
"mcp_servers": {
|
|
"fs": {
|
|
"command": "mcp-server",
|
|
"tools": {"include": ["read_file"]},
|
|
},
|
|
},
|
|
}
|
|
include_one = server._mcp_tools_list({"server": "fs"}, "default", servers, lock)
|
|
|
|
server._read_mcp_config = lambda profile: {
|
|
"mcp_servers": {
|
|
"fs": {
|
|
"command": "mcp-server",
|
|
"tools": {"exclude": ["delete_file"]},
|
|
},
|
|
},
|
|
}
|
|
exclude_one = server._mcp_tools_list({"server": "fs"}, "default", servers, lock)
|
|
|
|
print(json.dumps({
|
|
"include_empty": names(include_empty),
|
|
"include_empty_details": include_empty_list["servers"][0]["tool_details"],
|
|
"include_empty_raw": names(include_empty_raw),
|
|
"include_one": names(include_one),
|
|
"exclude_one": names(exclude_one),
|
|
}))
|
|
`)
|
|
|
|
expect(result).toEqual({
|
|
include_empty: [],
|
|
include_empty_details: [],
|
|
include_empty_raw: ['read_file', 'write_file', 'delete_file'],
|
|
include_one: ['read_file'],
|
|
exclude_one: ['read_file', 'write_file'],
|
|
})
|
|
})
|
|
})
|