LibreChat/packages/mcp/src/demo/everything.ts
Danny Avila e391347b9e
🔧 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
2024-12-17 13:12:57 -05:00

231 lines
5.6 KiB
TypeScript

import express from 'express';
import { EventSource } from 'eventsource';
import { MCPConnection } from '../connection';
import type { MCPOptions } from '../types/mcp';
// Set up EventSource for Node environment
global.EventSource = EventSource;
const app = express();
app.use(express.json());
let mcp: MCPConnection;
const initializeMCP = async () => {
console.log('Initializing MCP with SSE transport...');
const mcpOptions: MCPOptions = {
type: 'sse' as const,
url: 'http://localhost:3001/sse',
// type: 'stdio' as const,
// 'command': 'npx',
// 'args': [
// '-y',
// '@modelcontextprotocol/server-everything',
// ],
};
try {
await MCPConnection.destroyInstance();
mcp = MCPConnection.getInstance('everything', mcpOptions);
mcp.on('connectionChange', (state) => {
console.log(`MCP connection state changed to: ${state}`);
});
mcp.on('error', (error) => {
console.error('MCP error:', error);
});
console.log('Connecting to MCP server...');
await mcp.connectClient();
console.log('Connected to MCP server');
// Test the connection
try {
const resources = await mcp.fetchResources();
console.log('Available resources:', resources);
} catch (error) {
console.error('Error fetching resources:', error);
}
} catch (error) {
console.error('Failed to connect to MCP server:', error);
}
};
// API Endpoints
app.get('/status', (req, res) => {
res.json({
connected: mcp.isConnected(),
state: mcp.getConnectionState(),
error: mcp.getLastError()?.message,
});
});
// Resources endpoint
app.get('/resources', async (req, res) => {
try {
const resources = await mcp.fetchResources();
res.json({ resources });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Tools endpoint with all tool operations
app.get('/tools', async (req, res) => {
try {
const tools = await mcp.fetchTools();
res.json({ tools });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Echo tool endpoint
app.post('/tools/echo', async (req, res) => {
try {
const { message } = req.body;
const result = await mcp.client.callTool({
name: 'echo',
arguments: { message },
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Add tool endpoint
app.post('/tools/add', async (req, res) => {
try {
const { a, b } = req.body;
const result = await mcp.client.callTool({
name: 'add',
arguments: { a, b },
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Long running operation endpoint
app.post('/tools/long-operation', async (req, res) => {
try {
const { duration, steps } = req.body;
const result = await mcp.client.callTool({
name: 'longRunningOperation',
arguments: { duration, steps },
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Sample LLM endpoint
app.post('/tools/sample', async (req, res) => {
try {
const { prompt, maxTokens } = req.body;
const result = await mcp.client.callTool({
name: 'sampleLLM',
arguments: { prompt, maxTokens },
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Get tiny image endpoint
app.get('/tools/tiny-image', async (req, res) => {
try {
const result = await mcp.client.callTool({
name: 'getTinyImage',
arguments: {},
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Prompts endpoints
app.get('/prompts', async (req, res) => {
try {
const prompts = await mcp.fetchPrompts();
res.json({ prompts });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.post('/prompts/simple', async (req, res) => {
try {
const result = await mcp.client.getPrompt({
name: 'simple_prompt',
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.post('/prompts/complex', async (req, res) => {
try {
const { temperature, style } = req.body;
const result = await mcp.client.getPrompt({
name: 'complex_prompt',
arguments: { temperature, style },
});
res.json(result);
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Resource subscription endpoints
app.post('/resources/subscribe', async (req, res) => {
try {
const { uri } = req.body;
await mcp.client.subscribeResource({ uri });
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
app.post('/resources/unsubscribe', async (req, res) => {
try {
const { uri } = req.body;
await mcp.client.unsubscribeResource({ uri });
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: String(error) });
}
});
// Error handling
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error('Unhandled error:', err);
res.status(500).json({
error: 'Internal server error',
message: err.message,
});
});
// Cleanup on shutdown
process.on('SIGINT', async () => {
console.log('Shutting down...');
await MCPConnection.destroyInstance();
process.exit(0);
});
// Start server
const PORT = process.env.MCP_PORT ?? 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
initializeMCP();
});