diff --git a/docs/images/mcp/mcp-tc01-initial-state.png b/docs/images/mcp/mcp-tc01-initial-state.png new file mode 100644 index 0000000..1760198 Binary files /dev/null and b/docs/images/mcp/mcp-tc01-initial-state.png differ diff --git a/docs/images/mcp/mcp-tc02-search-git.png b/docs/images/mcp/mcp-tc02-search-git.png new file mode 100644 index 0000000..d12e85c Binary files /dev/null and b/docs/images/mcp/mcp-tc02-search-git.png differ diff --git a/docs/images/mcp/mcp-tc03-search-empty.png b/docs/images/mcp/mcp-tc03-search-empty.png new file mode 100644 index 0000000..ea3ed8b Binary files /dev/null and b/docs/images/mcp/mcp-tc03-search-empty.png differ diff --git a/docs/images/mcp/mcp-tc04-add-modal.png b/docs/images/mcp/mcp-tc04-add-modal.png new file mode 100644 index 0000000..081ae65 Binary files /dev/null and b/docs/images/mcp/mcp-tc04-add-modal.png differ diff --git a/docs/images/mcp/mcp-tc05-edit-modal.png b/docs/images/mcp/mcp-tc05-edit-modal.png new file mode 100644 index 0000000..4e8fe26 Binary files /dev/null and b/docs/images/mcp/mcp-tc05-edit-modal.png differ diff --git a/docs/images/mcp/mcp-tc06-tools-expanded.png b/docs/images/mcp/mcp-tc06-tools-expanded.png new file mode 100644 index 0000000..4c778dd Binary files /dev/null and b/docs/images/mcp/mcp-tc06-tools-expanded.png differ diff --git a/docs/images/mcp/mcp-tc07-responsive-768.png b/docs/images/mcp/mcp-tc07-responsive-768.png new file mode 100644 index 0000000..9d0a3dc Binary files /dev/null and b/docs/images/mcp/mcp-tc07-responsive-768.png differ diff --git a/docs/images/mcp/mcp-tc08-responsive-480.png b/docs/images/mcp/mcp-tc08-responsive-480.png new file mode 100644 index 0000000..9c1626d Binary files /dev/null and b/docs/images/mcp/mcp-tc08-responsive-480.png differ diff --git a/packages/client/src/api/hermes/mcp.ts b/packages/client/src/api/hermes/mcp.ts new file mode 100644 index 0000000..d3f9cb3 --- /dev/null +++ b/packages/client/src/api/hermes/mcp.ts @@ -0,0 +1,87 @@ +import { request } from '../client' + +export interface McpServerInfo { + name: string + transport: 'stdio' | 'http' | 'sse' + connected: boolean + tools: number + tools_registered: number + tool_names: string[] + tool_names_registered: string[] + tool_details: Array<{ name: string; description?: string }> + error?: string | null + raw_config: McpServerConfig +} + +export interface McpServersResponse { + ok: boolean + servers: McpServerInfo[] + total_tools: number + error?: string +} + +export interface McpToolsResponse { + ok: boolean + results: Array<{ + server: string + tools: Array<{ + name: string + description: string + input_schema: Record + }> + }> + error?: string +} + +export interface McpServerConfig { + command?: string + args?: string[] + url?: string + env?: Record + headers?: Record + timeout?: number + connect_timeout?: number + enabled?: boolean + transport?: 'stdio' | 'http' | 'sse' + tools?: { include?: string[]; exclude?: string[] } + prompts?: boolean + resources?: boolean +} + +export async function fetchMcpServers(): Promise { + return request('/api/hermes/mcp/servers') +} + +export async function fetchMcpTools(server?: string): Promise { + const query = server ? `?server=${encodeURIComponent(server)}` : '' + return request(`/api/hermes/mcp/tools${query}`) +} + +export async function mcpServerAdd(name: string, config: McpServerConfig): Promise<{ ok: boolean; name?: string; error?: string }> { + return request('/api/hermes/mcp/servers', { + method: 'POST', + body: JSON.stringify({ name, config }), + }) +} + +export async function mcpServerRemove(name: string): Promise<{ ok: boolean; error?: string }> { + return request(`/api/hermes/mcp/servers/${encodeURIComponent(name)}`, { + method: 'DELETE', + }) +} + +export async function mcpServerUpdate(name: string, config: McpServerConfig): Promise<{ ok: boolean; error?: string }> { + return request(`/api/hermes/mcp/servers/${encodeURIComponent(name)}`, { + method: 'PATCH', + body: JSON.stringify({ config }), + }) +} + +export async function mcpReload(name?: string): Promise<{ ok: boolean; message?: string; error?: string }> { + const query = name ? `?server=${encodeURIComponent(name)}` : '' + return request(`/api/hermes/mcp/reload${query}`, { method: 'POST' }) +} + +export async function mcpServerTest(name: string): Promise<{ ok: boolean; tools?: string[]; error?: string }> { + return request(`/api/hermes/mcp/servers/${encodeURIComponent(name)}/test`, { method: 'POST' }) +} diff --git a/packages/client/src/components/hermes/chat/ChatInput.vue b/packages/client/src/components/hermes/chat/ChatInput.vue index c863860..857821e 100644 --- a/packages/client/src/components/hermes/chat/ChatInput.vue +++ b/packages/client/src/components/hermes/chat/ChatInput.vue @@ -44,6 +44,7 @@ const bridgeCommands = computed(() => [ { name: 'compress', args: '', description: t('chat.slashCommands.compress') }, { name: 'steer', args: t('chat.slashCommandArgs.text'), description: t('chat.slashCommands.steer') }, { name: 'destroy', args: '', description: t('chat.slashCommands.destroy') }, + { name: 'reload-mcp', args: '', description: t('chat.slashCommands.reloadMcp') }, ]) const slashActive = ref(false) diff --git a/packages/client/src/components/hermes/mcp/McpServerCard.vue b/packages/client/src/components/hermes/mcp/McpServerCard.vue new file mode 100644 index 0000000..7733e96 --- /dev/null +++ b/packages/client/src/components/hermes/mcp/McpServerCard.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/packages/client/src/components/layout/AppSidebar.vue b/packages/client/src/components/layout/AppSidebar.vue index 27a8262..7472387 100644 --- a/packages/client/src/components/layout/AppSidebar.vue +++ b/packages/client/src/components/layout/AppSidebar.vue @@ -32,6 +32,9 @@ const isVersionPreview = import.meta.env.VITE_HERMES_PREVIEW === '1'; function isNavActive(...names: string[]) { return names.includes(selectedKey.value); } +function hasRoute(name: string): boolean { + return router.hasRoute(name); +} const logoPath = '/logo.png'; const { record: collapsedGroups, persist: persistCollapsedGroups } = usePersistentRecord('hermes.sidebar.collapsedGroups'); @@ -186,6 +189,15 @@ function openChangelog() { {{ t("sidebar.plugins") }} + + + + + + + + {{ t("sidebar.mcp") }} + @@ -263,7 +275,7 @@ function openChangelog() {