UNPKG

@mcp-shark/mcp-shark

Version:

Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.

184 lines (160 loc) 5.37 kB
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; const MCP_SERVER_BASE_URL = 'http://localhost:9851/mcp'; // Store client connections per server and session const clientSessions = new Map(); function getSessionKey(serverName, sessionId) { return `${serverName}:${sessionId}`; } export function createPlaygroundRoutes() { const router = {}; // Get or create client for a session and server async function getClient(serverName, sessionId) { const sessionKey = getSessionKey(serverName, sessionId); if (clientSessions.has(sessionKey)) { return clientSessions.get(sessionKey); } if (!serverName) { throw new Error('Server name is required'); } const mcpServerUrl = `${MCP_SERVER_BASE_URL}/${encodeURIComponent(serverName)}`; const client = new Client( { name: 'mcp-shark-playground', version: '1.0.0' }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } ); const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl)); await client.connect(transport); const clientWrapper = { client, transport, close: async () => { await client.close(); transport.close?.(); }, }; clientSessions.set(sessionKey, clientWrapper); return clientWrapper; } router.proxyRequest = async (req, res) => { try { const { method, params, serverName } = req.body; if (!method) { return res.status(400).json({ error: 'Invalid request', message: 'method field is required', }); } if (!serverName) { return res.status(400).json({ error: 'Invalid request', message: 'serverName field is required', }); } // Get or create session ID const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id'] || `playground-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const { client } = await getClient(serverName, sessionId); let result; switch (method) { case 'tools/list': result = await client.listTools(); break; case 'tools/call': if (!params?.name) { return res.status(400).json({ error: 'Invalid request', message: 'Tool name is required', }); } result = await client.callTool({ name: params.name, arguments: params.arguments || {}, }); break; case 'prompts/list': result = await client.listPrompts(); break; case 'prompts/get': if (!params?.name) { return res.status(400).json({ error: 'Invalid request', message: 'Prompt name is required', }); } result = await client.getPrompt({ name: params.name, arguments: params.arguments || {}, }); break; case 'resources/list': result = await client.listResources(); break; case 'resources/read': if (!params?.uri) { return res.status(400).json({ error: 'Invalid request', message: 'Resource URI is required', }); } result = await client.readResource({ uri: params.uri }); break; default: return res.status(400).json({ error: 'Unsupported method', message: `Method ${method} is not supported`, }); } // Return session ID in response res.setHeader('Mcp-Session-Id', sessionId); res.json({ result, _sessionId: sessionId, }); } catch (error) { console.error('Error in playground proxy:', error); // Check if it's a connection error if (error.message?.includes('ECONNREFUSED') || error.message?.includes('connect')) { return res.status(503).json({ error: 'MCP server unavailable', message: error.message, details: 'Make sure the MCP Shark server is running on port 9851', }); } res.status(500).json({ error: 'Internal server error', message: error.message || 'Unknown error', }); } }; // Cleanup endpoint to close client connections router.cleanup = async (req, res) => { const sessionId = req.headers['mcp-session-id'] || req.headers['x-mcp-session-id']; const { serverName } = req.body || {}; if (serverName && sessionId) { const sessionKey = getSessionKey(serverName, sessionId); if (clientSessions.has(sessionKey)) { const clientWrapper = clientSessions.get(sessionKey); await clientWrapper.close(); clientSessions.delete(sessionKey); } } else if (sessionId) { // Cleanup all sessions for this sessionId across all servers for (const [key, clientWrapper] of clientSessions.entries()) { if (key.endsWith(`:${sessionId}`)) { await clientWrapper.close(); clientSessions.delete(key); } } } res.json({ success: true }); }; return router; }