🔧 feat: Initial MCP Support (Tools) (#5015)

* 📝 chore: Add comment to clarify purpose of check_updates.sh script

* feat: mcp package

* feat: add librechat-mcp package and update dependencies

* feat: refactor MCPConnectionSingleton to handle transport initialization and connection management

* feat: change private methods to public in MCPConnectionSingleton for improved accessibility

* feat: filesystem demo

* chore: everything demo and move everything under mcp workspace

* chore: move ts-node to mcp workspace

* feat: mcp examples

* feat: working sse MCP example

* refactor: rename MCPConnectionSingleton to MCPConnection for clarity

* refactor: replace MCPConnectionSingleton with MCPConnection for consistency

* refactor: manager/connections

* refactor: update MCPConnection to use type definitions from mcp types

* refactor: update MCPManager to use winston logger and enhance server initialization

* refactor: share logger between connections and manager

* refactor: add schema definitions and update MCPManager to accept logger parameter

* feat: map available MCP tools

* feat: load manifest tools

* feat: add MCP tools delimiter constant and update plugin key generation

* feat: call MCP tools

* feat: update librechat-data-provider version to 0.7.63 and enhance StdioOptionsSchema with additional properties

* refactor: simplify typing

* chore: update types/packages

* feat: MCP Tool Content parsing

* chore: update dependencies and improve package configurations

* feat: add 'mcp' directory to package and update configurations

* refactor: return CONTENT_AND_ARTIFACT format for MCP callTool

* chore: bump @librechat/agents

* WIP: MCP artifacts

* chore: bump @librechat/agents to v1.8.7

* fix: ensure filename has extension when saving base64 image

* fix: move base64 buffer conversion before filename extension check

* chore: update backend review workflow to install MCP package

* fix: use correct `mime` method

* fix: enhance file metadata with message and tool call IDs in image saving process

* fix: refactor ToolCall component to handle MCP tool calls and improve domain extraction

* fix: update ToolItem component for default isInstalled value and improve localization in ToolSelectDialog

* fix: update ToolItem component to use consistent text color for tool description

* style: add theming to ToolSelectDialog

* fix: improve domain extraction logic in ToolCall component

* refactor: conversation item theming, fix rename UI bug, optimize props, add missing types

* feat: enhance MCP options schema with base options (iconPath to start) and make transport type optional, infer based on other option fields

* fix: improve reconnection logic with parallel init and exponential backoff and enhance transport debug logging

* refactor: improve logging format

* refactor: improve logging of available tools by displaying tool names

* refactor: improve reconnection/connection logic

* feat: add MCP package build process to Dockerfile

* feat: add fallback icon for tools without an image in ToolItem component

* feat: Assistants Support for MCP Tools

* fix(build): configure rollup to use output.dir for dynamic imports

* chore: update @librechat/agents to version 1.8.8 and add @langchain/anthropic dependency

* fix: update CONFIG_VERSION to 1.2.0
This commit is contained in:
Danny Avila 2024-12-17 13:12:57 -05:00 committed by GitHub
parent 0a97ad3915
commit e391347b9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 4322 additions and 234 deletions

View file

@ -1,4 +1,5 @@
#!/bin/bash
# SCRIPT USED TO DETERMINE WHICH PACKAGE HAD CHANGES
# Set the directory containing the package.json file
dir=${1:-.}

View file

@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.7.62",
"version": "0.7.63",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",
@ -39,11 +39,8 @@
},
"homepage": "https://librechat.ai",
"dependencies": {
"@types/js-yaml": "^4.0.9",
"axios": "^1.7.7",
"js-yaml": "^4.1.0",
"openai": "4.11.1",
"openapi-types": "^12.1.3",
"zod": "^3.22.4"
},
"devDependencies": {
@ -57,10 +54,13 @@
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.3.0",
"@types/react": "^18.2.18",
"jest": "^29.5.0",
"jest-junit": "^16.0.0",
"openai": "^4.76.3",
"openapi-types": "^12.1.3",
"rimraf": "^5.0.1",
"rollup": "^4.22.4",
"rollup-plugin-generate-package-json": "^3.2.0",

View file

@ -10,7 +10,7 @@ const entryPath = path.resolve(rootPath, 'api/server/index.js');
console.log('entryPath', entryPath);
// Define your custom aliases here
// Define custom aliases here
const customAliases = {
entries: [{ find: '~', replacement: rootServerPath }],
};
@ -18,7 +18,7 @@ const customAliases = {
export default {
input: entryPath,
output: {
file: 'test_bundle/bundle.js',
dir: 'test_bundle',
format: 'cjs',
},
plugins: [

View file

@ -1,11 +1,12 @@
/* eslint-disable max-len */
import { z } from 'zod';
import type { ZodError } from 'zod';
import type { TModelsConfig } from './types';
import { EModelEndpoint, eModelEndpointSchema } from './schemas';
import { fileConfigSchema } from './file-config';
import { specsConfigSchema } from './models';
import { FileSources } from './types/files';
import { TModelsConfig } from './types';
import { MCPServersSchema } from './mcp';
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord'];
@ -432,6 +433,7 @@ export const configSchema = z.object({
imageOutputType: z.nativeEnum(EImageOutputType).default(EImageOutputType.PNG),
includedTools: z.array(z.string()).optional(),
filteredTools: z.array(z.string()).optional(),
mcpServers: MCPServersSchema.optional(),
interface: z
.object({
privacyPolicy: z
@ -1086,7 +1088,7 @@ export enum Constants {
/** Key for the app's version. */
VERSION = 'v0.7.5',
/** Key for the Custom Config's version (librechat.yaml). */
CONFIG_VERSION = '1.1.9',
CONFIG_VERSION = '1.2.0',
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
NO_PARENT = '00000000-0000-0000-0000-000000000000',
/** Standard value for the initial conversationId before a request is sent */
@ -1109,6 +1111,8 @@ export enum Constants {
MAX_CONVO_STARTERS = 4,
/** Global/instance Project Name */
GLOBAL_PROJECT_NAME = 'instance',
/** Delimiter for MCP tools */
mcp_delimiter = '_mcp_',
}
export enum LocalStorageKeys {

View file

@ -11,6 +11,8 @@ export * from './zod';
/* custom/dynamic configurations */
export * from './generate';
export * from './models';
/* mcp */
export * from './mcp';
/* RBAC */
export * from './roles';
/* types (exports schemas from `./types` as they contain needed in other defs) */

View file

@ -0,0 +1,71 @@
import { z } from 'zod';
const BaseOptionsSchema = z.object({
iconPath: z.string().optional(),
});
export const StdioOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('stdio').optional(),
/**
* The executable to run to start the server.
*/
command: z.string(),
/**
* Command line arguments to pass to the executable.
*/
args: z.array(z.string()),
/**
* The environment to use when spawning the process.
*
* If not specified, the result of getDefaultEnvironment() will be used.
*/
env: z.record(z.string(), z.string()).optional(),
/**
* How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.
*
* @type {import('node:child_process').IOType | import('node:stream').Stream | number}
*
* The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr.
*/
stderr: z.any().optional(),
});
export const WebSocketOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('websocket').optional(),
url: z
.string()
.url()
.refine(
(val) => {
const protocol = new URL(val).protocol;
return protocol === 'ws:' || protocol === 'wss:';
},
{
message: 'WebSocket URL must start with ws:// or wss://',
},
),
});
export const SSEOptionsSchema = BaseOptionsSchema.extend({
type: z.literal('sse').optional(),
url: z
.string()
.url()
.refine(
(val) => {
const protocol = new URL(val).protocol;
return protocol !== 'ws:' && protocol !== 'wss:';
},
{
message: 'SSE URL must not start with ws:// or wss://',
},
),
});
export const MCPOptionsSchema = z.union([
StdioOptionsSchema,
WebSocketOptionsSchema,
SSEOptionsSchema,
]);
export const MCPServersSchema = z.record(z.string(), MCPOptionsSchema);

View file

@ -369,7 +369,7 @@ export const tPluginSchema = z.object({
name: z.string(),
pluginKey: z.string(),
description: z.string(),
icon: z.string(),
icon: z.string().optional(),
authConfig: z.array(tPluginAuthConfigSchema).optional(),
authenticated: z.boolean().optional(),
isButton: z.boolean().optional(),

View file

@ -1,4 +1,4 @@
import OpenAI from 'openai';
import type OpenAI from 'openai';
import type { InfiniteData } from '@tanstack/react-query';
import type {
TMessage,
@ -12,8 +12,6 @@ import type {
} from './schemas';
import type { TSpecsConfig } from './models';
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
export type TOpenAIFunction = OpenAI.Chat.ChatCompletionCreateParams.Function;
export type TOpenAIFunctionCall = OpenAI.Chat.ChatCompletionCreateParams.FunctionCallOption;
export * from './schemas';