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:
@@ -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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user