mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
working pass to backend
This commit is contained in:
parent
dc03986149
commit
7a73d2daf3
10 changed files with 302 additions and 47 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
154
packages/api/src/mcp/add.ts
Normal 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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue