No MCP servers available
@@ -116,7 +395,7 @@ function App() {
) : (
{servers.map((server, index) => (
-
+
))}
)}
@@ -125,4 +404,5 @@ function App() {
);
}
+// 使用兼容性更好的渲染方式
ReactDOM.render(
, document.getElementById('root'));
diff --git a/src/index.ts b/src/index.ts
index f5b4462..35267a9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,7 +2,7 @@ import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
-import { registerAllTools, getServersInfo } from './server.js';
+import { registerAllTools, getServersInfo, getServersSettings, addServer, removeServer } from './server.js';
import path from 'path';
dotenv.config();
@@ -21,6 +21,9 @@ const PORT = process.env.PORT || 3000;
// Serve static files from the public directory
app.use(express.static('public'));
+// Parse JSON request body
+app.use(express.json());
+
// to support multiple simultaneous connections we have a lookup object from sessionId to transport
const transports: { [sessionId: string]: SSEServerTransport } = {};
@@ -30,6 +33,56 @@ app.get('/api/servers', (req: Request, res: Response) => {
res.json(serversInfo);
});
+// API endpoint to get all server settings
+app.get('/api/settings', (req: Request, res: Response) => {
+ const settings = getServersSettings();
+ res.json(settings);
+});
+
+// API endpoint to add a new server
+app.post('/api/servers', async (req: Request, res: Response) => {
+ const { name, config } = req.body;
+
+ if (!name || typeof name !== 'string') {
+ return res.status(400).json({ success: false, message: 'Server name is required' });
+ }
+
+ if (!config || typeof config !== 'object') {
+ return res.status(400).json({ success: false, message: 'Server configuration is required' });
+ }
+
+ // Validate config has either url or command+args
+ if (!config.url && (!config.command || !config.args)) {
+ return res.status(400).json({
+ success: false,
+ message: 'Server configuration must include either a URL or command with arguments',
+ });
+ }
+
+ const success = await addServer(server, name, config);
+ if (success) {
+ res.json({ success: true, message: 'Server added successfully' });
+ } else {
+ res.status(400).json({ success: false, message: 'Failed to add server' });
+ }
+});
+
+// API endpoint to remove a server
+app.delete('/api/servers/:name', (req: Request, res: Response) => {
+ const { name } = req.params;
+
+ if (!name) {
+ return res.status(400).json({ success: false, message: 'Server name is required' });
+ }
+
+ const success = removeServer(name);
+ if (success) {
+ res.json({ success: true, message: 'Server removed successfully' });
+ } else {
+ res.status(404).json({ success: false, message: 'Server not found or failed to remove' });
+ }
+});
+
app.get('/sse', async (_: Request, res: Response) => {
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;
diff --git a/src/server.ts b/src/server.ts
index 3e9b046..6f705de 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -45,6 +45,18 @@ function loadSettings(): McpSettings {
}
}
+// Function to save settings to file
+export function saveSettings(settings: McpSettings): boolean {
+ const settingsPath = path.resolve(process.cwd(), 'mcp_settings.json');
+ try {
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
+ return true;
+ } catch (error) {
+ console.error(`Failed to save settings to ${settingsPath}:`, error);
+ return false;
+ }
+}
+
// Initialize clients and transports from settings
function initializeClientsFromSettings(): {
servers: string[];
@@ -98,7 +110,7 @@ function initializeClientsFromSettings(): {
}
// Initialize clients and transports
-const { servers, clients, transports } = initializeClientsFromSettings();
+let { servers, clients, transports } = initializeClientsFromSettings();
// Keep track of connected clients and their tools
const clientTools: { [clientIndex: number]: ToolInfo[] } = {};
@@ -149,6 +161,78 @@ export function getServersInfo(): ServerInfo[] {
}));
}
+// Add function to get all server settings
+export function getServersSettings(): McpSettings {
+ return loadSettings();
+}
+
+// Add function to add a new server
+export async function addServer(mcpServer: McpServer, name: string, config: { url?: string; command?: string; args?: string[]; env?: Record
}): Promise {
+ try {
+ // Load current settings
+ const settings = loadSettings();
+
+ // Check if server with this name already exists
+ if (settings.mcpServers[name]) {
+ return false;
+ }
+
+ // Add new server to settings
+ settings.mcpServers[name] = config;
+
+ // Save updated settings
+ if (!saveSettings(settings)) {
+ return false;
+ }
+
+ // Re-initialize clients with updated settings
+ const result = initializeClientsFromSettings();
+ servers = result.servers;
+ clients = result.clients;
+ transports = result.transports;
+
+ // Register tools for the new server
+ await registerAllTools(mcpServer);
+
+ return true;
+ } catch (error) {
+ console.error(`Failed to add server: ${name}`, error);
+ return false;
+ }
+}
+
+// Add function to remove a server
+export function removeServer(name: string): boolean {
+ try {
+ // Load current settings
+ const settings = loadSettings();
+
+ // Check if server exists
+ if (!settings.mcpServers[name]) {
+ return false;
+ }
+
+ // Remove server from settings
+ delete settings.mcpServers[name];
+
+ // Save updated settings
+ if (!saveSettings(settings)) {
+ return false;
+ }
+
+ // Re-initialize clients with updated settings
+ const result = initializeClientsFromSettings();
+ servers = result.servers;
+ clients = result.clients;
+ transports = result.transports;
+
+ return true;
+ } catch (error) {
+ console.error(`Failed to remove server: ${name}`, error);
+ return false;
+ }
+}
+
function cast(inputSchema: unknown): ZodRawShape {
if (typeof inputSchema !== 'object' || inputSchema === null) {
throw new Error('Invalid input schema');