feat: MCP Client plugin (Task 5.17) - integrate external MCP servers for AI agents

- New plugin app/plugins/builtins/mcp_client/ with server config CRUD
- Models: McpServerConfig with TenantMixin (name, url, api_token, enabled)
- Routes: GET/POST/PATCH/DELETE /api/v1/mcp-client/servers
- Routes: GET /servers/{id}/tools, POST /servers/{id}/execute
- client.py: async MCP client using httpx for external server calls
- tool_registry_integration.py: registers external MCP tools in AI tool registry
- Migration: 0001_initial.sql for mcp_server_configs table
- Frontend: mcpClient.ts API client with React Query hooks
- Frontend: MCP Client settings UI in SettingsMcp.tsx (server CRUD, tool viewing)
- Tests: 8 tests covering CRUD, auth, tool registry integration
This commit is contained in:
Agent Zero
2026-07-23 23:02:07 +02:00
parent 9d4f701a25
commit 317d5c81f8
10 changed files with 867 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
/**
* MCP Client plugin API client.
*
* Manages external MCP server configurations and tool execution.
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost, apiPatch, apiDelete } from './client';
// ─── Types ─────────────────────────────────────────────────────────────────
export interface McpServerConfigEntry {
id: string;
name: string;
url: string;
api_token: string | null;
enabled: boolean;
description: string | null;
last_connected_at: string | null;
created_by: string | null;
created_at: string | null;
updated_at: string | null;
}
export interface McpServerConfigCreate {
name: string;
url: string;
api_token?: string | null;
enabled?: boolean;
description?: string | null;
}
export interface McpServerConfigUpdate {
name?: string;
url?: string;
api_token?: string | null;
enabled?: boolean;
description?: string | null;
}
export interface McpServerToolInfo {
name: string;
description: string;
parameters: Record<string, unknown>;
}
export interface McpServerToolsResponse {
server_name: string;
server_url: string;
tools: McpServerToolInfo[];
count: number;
}
export interface McpServerExecuteRequest {
tool_name: string;
arguments: Record<string, unknown>;
}
export interface McpServerExecuteResponse {
server_name: string;
tool: string;
success: boolean;
result: Record<string, unknown> | string | null;
error: string | null;
}
// ─── API Functions ─────────────────────────────────────────────────────────
export function fetchMcpServers(): Promise<McpServerConfigEntry[]> {
return apiGet<McpServerConfigEntry[]>('/mcp-client/servers');
}
export function createMcpServer(
payload: McpServerConfigCreate
): Promise<McpServerConfigEntry> {
return apiPost<McpServerConfigEntry>('/mcp-client/servers', payload);
}
export function updateMcpServer(
id: string,
payload: McpServerConfigUpdate
): Promise<McpServerConfigEntry> {
return apiPatch<McpServerConfigEntry>(`/mcp-client/servers/${id}`, payload);
}
export function deleteMcpServer(id: string): Promise<void> {
return apiDelete<void>(`/mcp-client/servers/${id}`);
}
export function fetchMcpServerTools(
serverId: string
): Promise<McpServerToolsResponse> {
return apiGet<McpServerToolsResponse>(`/mcp-client/servers/${serverId}/tools`);
}
export function executeMcpServerTool(
serverId: string,
payload: McpServerExecuteRequest
): Promise<McpServerExecuteResponse> {
return apiPost<McpServerExecuteResponse>(
`/mcp-client/servers/${serverId}/execute`,
payload
);
}
// ─── Hooks ─────────────────────────────────────────────────────────────────
export function useMcpServers() {
return useQuery({
queryKey: ['mcp-client', 'servers'],
queryFn: fetchMcpServers,
staleTime: 30 * 1000,
});
}
export function useCreateMcpServer() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createMcpServer,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['mcp-client', 'servers'] });
},
});
}
export function useUpdateMcpServer() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
payload,
}: {
id: string;
payload: McpServerConfigUpdate;
}) => updateMcpServer(id, payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['mcp-client', 'servers'] });
},
});
}
export function useDeleteMcpServer() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: deleteMcpServer,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['mcp-client', 'servers'] });
},
});
}
export function useMcpServerTools(serverId: string | null) {
return useQuery({
queryKey: ['mcp-client', 'servers', serverId, 'tools'],
queryFn: () => fetchMcpServerTools(serverId!),
enabled: !!serverId,
staleTime: 60 * 1000,
});
}