diff --git a/api/server/routes/agents/tools.js b/api/server/routes/agents/tools.js index 8e498b1db8..ff2d25068e 100644 --- a/api/server/routes/agents/tools.js +++ b/api/server/routes/agents/tools.js @@ -1,4 +1,5 @@ const express = require('express'); +const { addTool } = require('@librechat/api'); const { callTool, verifyToolAuth, getToolCalls } = require('~/server/controllers/tools'); const { getAvailableTools } = require('~/server/controllers/PluginController'); const { toolCallLimiter } = require('~/server/middleware/limiters'); @@ -36,4 +37,12 @@ router.get('/:toolId/auth', verifyToolAuth); */ router.post('/:toolId/call', toolCallLimiter, callTool); +/** + * Add a new tool to the system + * @route POST /agents/tools/add + * @param {object} req.body - Request body containing tool data + * @returns {object} Created tool object + */ +router.post('/add', addTool); + module.exports = router; diff --git a/client/src/components/SidePanel/MCP/MCPPanel.tsx b/client/src/components/SidePanel/MCP/MCPPanel.tsx index aa2bf72112..ba757a4e45 100644 --- a/client/src/components/SidePanel/MCP/MCPPanel.tsx +++ b/client/src/components/SidePanel/MCP/MCPPanel.tsx @@ -6,6 +6,7 @@ import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-quer import type { TUpdateUserPlugins } from 'librechat-data-provider'; import { Button, Input, Label } from '~/components/ui'; import { useGetStartupConfig } from '~/data-provider'; +import { useAddToolMutation } from '~/data-provider/Tools'; import MCPPanelSkeleton from './MCPPanelSkeleton'; import { useToastContext } from '~/Providers'; import { useLocalize } from '~/hooks'; @@ -17,6 +18,12 @@ interface ServerConfigWithVars { }; } +interface AddToolFormData { + name: string; + description: string; + type: 'function' | 'code_interpreter' | 'file_search'; +} + export default function MCPPanel() { const localize = useLocalize(); const { showToast } = useToastContext(); @@ -24,6 +31,7 @@ export default function MCPPanel() { const [selectedServerNameForEditing, setSelectedServerNameForEditing] = useState( null, ); + const [showAddToolForm, setShowAddToolForm] = useState(true); const mcpServerDefinitions = useMemo(() => { if (!startupConfig?.mcpServers) { @@ -87,19 +95,25 @@ export default function MCPPanel() { const handleGoBackToList = () => { setSelectedServerNameForEditing(null); + setShowAddToolForm(false); + }; + + const handleShowAddToolForm = () => { + setShowAddToolForm(true); + setSelectedServerNameForEditing(null); }; if (startupConfigLoading) { return ; } - if (mcpServerDefinitions.length === 0) { - return ( -
- {localize('com_sidepanel_mcp_no_servers_with_vars')} -
- ); - } + // if (mcpServerDefinitions.length === 0) { + // return ( + //
+ // {localize('com_sidepanel_mcp_no_servers_with_vars')} + //
+ // ); + // } if (selectedServerNameForEditing) { // Editing View @@ -138,10 +152,32 @@ export default function MCPPanel() { /> ); + } else if (showAddToolForm) { + // Add Tool Form View + return ( +
+ +

{localize('com_ui_add_tool')}

+ +
+ ); } else { // Server List View return (
+
+

{localize('com_ui_mcp_servers')}

+ +
{mcpServerDefinitions.map((server) => ( + +
+ + ); +} diff --git a/client/src/data-provider/Tools/mutations.ts b/client/src/data-provider/Tools/mutations.ts index 0c87c6e5eb..3597b39997 100644 --- a/client/src/data-provider/Tools/mutations.ts +++ b/client/src/data-provider/Tools/mutations.ts @@ -40,3 +40,44 @@ export const useToolCallMutation = ( }, ); }; + +/** + * Interface for creating a new tool + */ +interface CreateToolData { + name: string; + description: string; + type: 'function' | 'code_interpreter' | 'file_search'; + metadata?: Record; +} + +/** + * Mutation hook for adding a new tool to the system + * Note: Requires corresponding backend implementation of dataService.createTool + */ +export const useAddToolMutation = ( + // options?: + // { + // onMutate?: (variables: CreateToolData) => void | Promise; + // onError?: (error: Error, variables: CreateToolData, context: unknown) => void; + // onSuccess?: (data: t.Tool, variables: CreateToolData, context: unknown) => void; + // } + options?: t.MutationOptions, CreateToolData>, +): UseMutationResult, Error, CreateToolData> => { + const queryClient = useQueryClient(); + + return useMutation( + (toolData: CreateToolData) => { + return dataService.createTool(toolData); + }, + { + onMutate: (variables) => options?.onMutate?.(variables), + onError: (error, variables, context) => options?.onError?.(error, variables, context), + onSuccess: (data, variables, context) => { + // Invalidate tools list to trigger refetch + queryClient.invalidateQueries([QueryKeys.tools]); + return options?.onSuccess?.(data, variables, context); + }, + }, + ); +}; diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index 35dd8957b8..a690f82f95 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -194,7 +194,7 @@ export const useConversationTagsQuery = ( /** * Hook for getting all available tools for Assistants */ -export const useAvailableToolsQuery = ( +export const useAvailableToolsQuery = ( // <-- this one endpoint: t.AssistantsEndpoint | EModelEndpoint.agents, config?: UseQueryOptions, ): QueryObserverResult => { diff --git a/client/src/hooks/Nav/useSideNavLinks.ts b/client/src/hooks/Nav/useSideNavLinks.ts index 13657c058e..11eef69331 100644 --- a/client/src/hooks/Nav/useSideNavLinks.ts +++ b/client/src/hooks/Nav/useSideNavLinks.ts @@ -152,20 +152,20 @@ export default function useSideNavLinks({ }); } - if ( - startupConfig?.mcpServers && - Object.values(startupConfig.mcpServers).some( - (server) => server.customUserVars && Object.keys(server.customUserVars).length > 0, - ) - ) { - links.push({ - title: 'com_nav_setting_mcp', - label: '', - icon: MCPIcon, - id: 'mcp-settings', - Component: MCPPanel, - }); - } + // if ( + // startupConfig?.mcpServers && + // Object.values(startupConfig.mcpServers).some( + // (server) => server.customUserVars && Object.keys(server.customUserVars).length > 0, + // ) + // ) { + links.push({ + title: 'com_nav_setting_mcp', + label: '', + icon: MCPIcon, + id: 'mcp-settings', + Component: MCPPanel, + }); + // } links.push({ title: 'com_sidepanel_hide_panel', diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 0341de44b0..b5266343d0 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -2,6 +2,7 @@ export * from './mcp/manager'; export * from './mcp/oauth'; export * from './mcp/auth'; +export * from './mcp/add'; /* Utilities */ export * from './mcp/utils'; export * from './utils'; diff --git a/packages/api/src/mcp/add.ts b/packages/api/src/mcp/add.ts new file mode 100644 index 0000000000..2a9433143b --- /dev/null +++ b/packages/api/src/mcp/add.ts @@ -0,0 +1,81 @@ +import { Request, Response } from 'express'; +import { logger } from '@librechat/data-schemas'; + +interface CreateToolRequest extends Request { + body: { + name: string; + description: string; + type: 'function' | 'code_interpreter' | 'file_search'; + metadata?: Record; + }; + user?: { + id: string; + }; +} + +/** + * Add a new tool to the system + * @route POST /agents/tools/add + * @param {object} req.body - Request body containing tool data + * @param {string} req.body.name - Tool name + * @param {string} req.body.description - Tool description + * @param {string} req.body.type - Tool type (function, code_interpreter, file_search) + * @param {object} [req.body.metadata] - Optional metadata + * @returns {object} Created tool object + */ +export const addTool = async (req: CreateToolRequest, res: Response) => { + try { + const { name, description, type, metadata } = req.body; + + // Log the incoming request for development + logger.info( + 'Add Tool Request:' + + JSON.stringify({ + name, + description, + type, + metadata, + userId: req.user?.id, + }), + ); + + // Validate required fields + if (!name || !description || !type) { + return res.status(400).json({ + error: 'Missing required fields: name, description, and type are required', + }); + } + + // Validate tool type + const validTypes = ['function', 'code_interpreter', 'file_search']; + if (!validTypes.includes(type)) { + return res.status(400).json({ + error: `Invalid tool type. Must be one of: ${validTypes.join(', ')}`, + }); + } + + // For now, return a mock successful response + // TODO: Implement actual tool creation logic + const mockTool = { + id: `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + type, + function: { + name, + description, + }, + metadata: metadata || {}, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }; + + logger.info('Tool created successfully:' + JSON.stringify(mockTool)); + + res.status(201).json(mockTool); + } catch (error) { + logger.error('Error adding tool:', error); + res.status(500).json({ + error: 'Internal server error while adding tool', + message: error instanceof Error ? error.message : 'Unknown error', + }); + } +}; diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index c76efbac87..8fca5f12af 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -312,6 +312,30 @@ export const getToolCalls = (params: q.GetToolCallParams): Promise; +}): Promise<{ + id: string; + type: string; + function: { + name: string; + description: string; + }; + metadata: Record; + created_at: string; + updated_at: string; +}> => { + return request.post( + endpoints.agents({ + path: 'tools/add', + }), + toolData, + ); +}; + /* Files */ export const getFiles = (): Promise => { diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index 93e21c7e45..786c29295f 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -48,6 +48,7 @@ export enum QueryKeys { banner = 'banner', /* Memories */ memories = 'memories', + mcpTools = 'mcpTools', } export enum MutationKeys {