feat: add API endpoints for managing servers and settings

This commit is contained in:
samanhappy
2025-04-01 18:24:14 +08:00
parent 251f03831f
commit 13b2ff4234
3 changed files with 424 additions and 7 deletions

View File

@@ -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;

View File

@@ -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<string, string> }): Promise<boolean> {
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');