working pass to backend

This commit is contained in:
Dustin Healy 2025-06-26 16:51:14 -07:00
parent dc03986149
commit 7a73d2daf3
10 changed files with 302 additions and 47 deletions

View file

@ -1,4 +1,5 @@
const express = require('express'); const express = require('express');
const { addTool } = require('@librechat/api');
const { callTool, verifyToolAuth, getToolCalls } = require('~/server/controllers/tools'); const { callTool, verifyToolAuth, getToolCalls } = require('~/server/controllers/tools');
const { getAvailableTools } = require('~/server/controllers/PluginController'); const { getAvailableTools } = require('~/server/controllers/PluginController');
const { toolCallLimiter } = require('~/server/middleware/limiters'); const { toolCallLimiter } = require('~/server/middleware/limiters');
@ -36,4 +37,12 @@ router.get('/:toolId/auth', verifyToolAuth);
*/ */
router.post('/:toolId/call', toolCallLimiter, callTool); router.post('/:toolId/call', toolCallLimiter, callTool);
/**
* Add a new tool/MCP to the system
* @route POST /agents/tools/add
* @param {object} req.body - Request body containing tool/MCP data
* @returns {object} Created tool/MCP object
*/
router.post('/add', addTool);
module.exports = router; module.exports = router;

View file

@ -1,26 +1,13 @@
import {
AuthorizationTypeEnum,
AuthTypeEnum,
TokenExchangeMethodEnum,
} from 'librechat-data-provider';
import { MCPForm } from '~/common/types'; import { MCPForm } from '~/common/types';
export const defaultMCPFormValues: MCPForm = { export const defaultMCPFormValues: MCPForm = {
type: AuthTypeEnum.None,
saved_auth_fields: false,
api_key: '',
authorization_type: AuthorizationTypeEnum.Basic,
custom_auth_header: '',
oauth_client_id: '',
oauth_client_secret: '',
authorization_url: '',
client_url: '',
scope: '',
token_exchange_method: TokenExchangeMethodEnum.DefaultPost,
name: '', name: '',
description: '', description: '',
url: '', url: '',
tools: [], tools: [],
icon: '', icon: '',
trust: false, trust: false,
customHeaders: [],
requestTimeout: undefined,
connectionTimeout: undefined,
}; };

View file

@ -9,11 +9,6 @@ import { defaultMCPFormValues } from '~/common/mcp';
import useLocalize from '~/hooks/useLocalize'; import useLocalize from '~/hooks/useLocalize';
import { TrashIcon } from '~/components/svg'; import { TrashIcon } from '~/components/svg';
import MCPInput from './MCPInput'; import MCPInput from './MCPInput';
import {
AuthTypeEnum,
AuthorizationTypeEnum,
TokenExchangeMethodEnum,
} from 'librechat-data-provider';
interface MCPFormPanelProps { interface MCPFormPanelProps {
// Data // Data
@ -66,24 +61,11 @@ export default function MCPFormPanel({
url: mcp.metadata.url ?? '', url: mcp.metadata.url ?? '',
tools: mcp.metadata.tools ?? [], tools: mcp.metadata.tools ?? [],
trust: mcp.metadata.trust ?? false, trust: mcp.metadata.trust ?? false,
customHeaders: mcp.metadata.customHeaders ?? [],
requestTimeout: mcp.metadata.requestTimeout,
connectionTimeout: mcp.metadata.connectionTimeout,
}; };
if (mcp.metadata.auth) {
Object.assign(formData, {
type: mcp.metadata.auth.type || AuthTypeEnum.None,
saved_auth_fields: false,
api_key: mcp.metadata.api_key ?? '',
authorization_type: mcp.metadata.auth.authorization_type || AuthorizationTypeEnum.Basic,
oauth_client_id: mcp.metadata.oauth_client_id ?? '',
oauth_client_secret: mcp.metadata.oauth_client_secret ?? '',
authorization_url: mcp.metadata.auth.authorization_url ?? '',
client_url: mcp.metadata.auth.client_url ?? '',
scope: mcp.metadata.auth.scope ?? '',
token_exchange_method:
mcp.metadata.auth.token_exchange_method ?? TokenExchangeMethodEnum.DefaultPost,
});
}
reset(formData); reset(formData);
} }
}, [mcp, reset]); }, [mcp, reset]);

View file

@ -7,6 +7,7 @@ import type { TUpdateUserPlugins } from 'librechat-data-provider';
import type { MCP } from 'librechat-data-provider'; import type { MCP } from 'librechat-data-provider';
import { Button, Input, Label } from '~/components/ui'; import { Button, Input, Label } from '~/components/ui';
import { useGetStartupConfig } from '~/data-provider'; import { useGetStartupConfig } from '~/data-provider';
import { useAddToolMutation } from '~/data-provider/Tools/mutations';
import MCPPanelSkeleton from './MCPPanelSkeleton'; import MCPPanelSkeleton from './MCPPanelSkeleton';
import { useToastContext } from '~/Providers'; import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
@ -60,6 +61,23 @@ export default function MCPPanel() {
}, },
}); });
const createMCPMutation = useAddToolMutation({
onSuccess: () => {
showToast({
message: localize('com_ui_update_mcp_success'),
status: 'success',
});
setShowMCPForm(false);
},
onError: (error) => {
console.error('Error creating MCP:', error);
showToast({
message: localize('com_ui_update_mcp_error'),
status: 'error',
});
},
});
const handleSaveServerVars = useCallback( const handleSaveServerVars = useCallback(
(serverName: string, updatedValues: Record<string, string>) => { (serverName: string, updatedValues: Record<string, string>) => {
const payload: TUpdateUserPlugins = { const payload: TUpdateUserPlugins = {
@ -101,13 +119,20 @@ export default function MCPPanel() {
}; };
const handleSaveMCP = (mcp: MCP) => { const handleSaveMCP = (mcp: MCP) => {
// TODO: Implement MCP save logic for conversation context // Transform MCP data to match the expected format
console.log('Saving MCP:', mcp); const mcpData = {
showToast({ name: mcp.metadata.name || '',
message: localize('com_ui_update_mcp_success'), description: mcp.metadata.description || '',
status: 'success', url: mcp.metadata.url || '',
}); icon: mcp.metadata.icon || '',
setShowMCPForm(false); tools: mcp.metadata.tools || [],
trust: mcp.metadata.trust ?? false,
customHeaders: mcp.metadata.customHeaders || [],
requestTimeout: mcp.metadata.requestTimeout,
connectionTimeout: mcp.metadata.connectionTimeout,
};
createMCPMutation.mutate(mcpData);
}; };
if (showMCPForm) { if (showMCPForm) {

View file

@ -40,3 +40,43 @@ export const useToolCallMutation = <T extends t.ToolId>(
}, },
); );
}; };
interface CreateToolData {
name: string;
description: string;
metadata?: Record<string, unknown>;
// MCP-specific fields
url?: string;
icon?: string;
tools?: string[];
trust?: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
}
export const useAddToolMutation = (
options?: t.MutationOptions<Record<string, unknown>, CreateToolData>,
): UseMutationResult<Record<string, unknown>, 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]);
queryClient.invalidateQueries([QueryKeys.mcpTools]);
return options?.onSuccess?.(data, variables, context);
},
},
);
};

View file

@ -2,6 +2,7 @@
export * from './mcp/manager'; export * from './mcp/manager';
export * from './mcp/oauth'; export * from './mcp/oauth';
export * from './mcp/auth'; export * from './mcp/auth';
export * from './mcp/add';
/* Utilities */ /* Utilities */
export * from './mcp/utils'; export * from './mcp/utils';
export * from './utils'; export * from './utils';

154
packages/api/src/mcp/add.ts Normal file
View file

@ -0,0 +1,154 @@
import { Request, Response } from 'express';
import { logger } from '@librechat/data-schemas';
interface MCPCreateRequest extends Request {
body: {
name: string;
description: string;
url: string;
icon?: string;
tools?: string[];
trust: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
};
user?: {
id: string;
};
}
export interface MCPCreateResponse {
mcp_id: string;
metadata: {
name: string;
description: string;
url: string;
icon?: string;
tools?: string[];
trust: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
};
created_at: string;
updated_at: string;
}
export interface MCPCreateError {
error: string;
message?: string;
}
/**
* Add a new tool/MCP to the system
* @route POST /agents/tools/add
* @param {object} req.body - Request body containing tool/MCP data
* @param {string} req.body.name - Tool/MCP name
* @param {string} req.body.description - Tool/MCP description
* @param {string} req.body.url - Tool/MCP server URL
* @param {string} [req.body.icon] - Tool/MCP icon (base64)
* @param {string[]} [req.body.tools] - Available tools
* @param {boolean} req.body.trust - Trust flag
* @param {string[]} [req.body.customHeaders] - Custom headers
* @param {string} [req.body.requestTimeout] - Request timeout
* @param {string} [req.body.connectionTimeout] - Connection timeout
* @returns {object} Created tool/MCP object
*/
export const addTool = async (req: MCPCreateRequest, res: Response) => {
try {
const {
name,
description,
url,
icon,
tools,
trust,
customHeaders,
requestTimeout,
connectionTimeout,
} = req.body;
// Log the raw request body for debugging
logger.info('Raw request body: ' + JSON.stringify(req.body, null, 2));
// Log the incoming tool/MCP request for development
const logData = {
name,
description,
url,
icon: icon ? 'base64_icon_provided' : 'no_icon',
tools: tools || [],
trust,
customHeaders: customHeaders || [],
timeouts: {
requestTimeout: requestTimeout || 'default',
connectionTimeout: connectionTimeout || 'default',
},
userId: req.user?.id,
timestamp: new Date().toISOString(),
};
logger.info('Add Tool/MCP Request: ' + JSON.stringify(logData, null, 2));
// Validate required fields
if (!name || !description || !url) {
return res.status(400).json({
error: 'Missing required fields: name, description, and url are required',
});
}
// Validate URL format
try {
new URL(url);
} catch {
return res.status(400).json({
error: 'Invalid URL format',
});
}
// Validate trust flag
if (typeof trust !== 'boolean') {
return res.status(400).json({
error: 'Trust flag must be a boolean value',
});
}
// For now, return a mock successful response
// TODO: Implement actual tool/MCP creation logic
const mockTool = {
mcp_id: `mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
metadata: {
name,
description,
url,
icon: icon || undefined,
tools: tools || [],
trust,
customHeaders: customHeaders || [],
requestTimeout,
connectionTimeout,
},
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
logger.info('Tool/MCP created successfully:', JSON.stringify(mockTool, null, 2));
res.status(201).json(mockTool);
} catch (error) {
logger.error('Error adding tool/MCP:', error);
res.status(500).json({
error: 'Internal server error while adding tool/MCP',
message: error instanceof Error ? error.message : 'Unknown error',
});
}
};

View file

@ -832,3 +832,55 @@ export const createMemory = (data: {
}): Promise<{ created: boolean; memory: q.TUserMemory }> => { }): Promise<{ created: boolean; memory: q.TUserMemory }> => {
return request.post(endpoints.memories(), data); return request.post(endpoints.memories(), data);
}; };
export const createTool = (toolData: {
name: string;
description: string;
metadata?: Record<string, unknown>;
// MCP-specific fields
url?: string;
icon?: string;
tools?: string[];
trust?: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
}): Promise<{
id?: string;
mcp_id?: string;
type?: string;
function?: {
name: string;
description: string;
};
metadata:
| Record<string, unknown>
| {
name: string;
description: string;
url?: string;
icon?: string;
tools?: string[];
trust?: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
};
created_at: string;
updated_at: string;
}> => {
return request.post(
endpoints.agents({
path: 'tools/add',
}),
toolData,
);
};

View file

@ -48,6 +48,7 @@ export enum QueryKeys {
banner = 'banner', banner = 'banner',
/* Memories */ /* Memories */
memories = 'memories', memories = 'memories',
mcpTools = 'mcpTools',
} }
export enum MutationKeys { export enum MutationKeys {

View file

@ -342,13 +342,17 @@ export type MCPMetadata = Omit<ActionMetadata, 'auth'> & {
description?: string; description?: string;
url?: string; url?: string;
tools?: string[]; tools?: string[];
auth?: MCPAuth;
icon?: string; icon?: string;
trust?: boolean; trust?: boolean;
customHeaders?: Array<{
id: string;
name: string;
value: string;
}>;
requestTimeout?: number;
connectionTimeout?: number;
}; };
export type MCPAuth = ActionAuth;
export type AgentToolType = { export type AgentToolType = {
tool_id: string; tool_id: string;
metadata: ToolMetadata; metadata: ToolMetadata;