import { Router, Request, Response, IRouter } from 'express';
import { useProxyHandles } from '@opentiny/agent';
import { logger } from '../../Logger';
import { config } from '../../config';
type ProxyHandles = ReturnType<typeof useProxyHandles>;
const proxyHandles: ProxyHandles = useProxyHandles();
const router: IRouter = Router();
router.get('/ping', async (req: Request, res: Response) => {
const startTime = Date.now();
try {
res.json(await proxyHandles.ping());
} catch (error) {
const responseTime = Date.now() - startTime;
logger.error('Ping endpoint error:', error);
res.status(500).json({
error: 'Ping check failed',
message: error instanceof Error ? error.message : 'Unknown error',
responseTime: `${responseTime}ms`,
timestamp: new Date().toISOString(),
});
}
});
router.get('/sse', async (req: Request, res: Response) => {
const startTime = Date.now();
const connectionId = `sse_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
try {
const sessionId = (req.query.sessionId as string) || null;
if (sessionId) {
logger.info(`SSE Inspector connection: ${connectionId} for session: ${sessionId}`);
try {
await proxyHandles.handleSseInspector(req, res, `${config.app.apiPrefix}/webmcp/messages`);
} catch (inspectorError) {
logger.error(`SSE Inspector error for connection ${connectionId}:`, inspectorError);
if (!res.headersSent) {
res.status(500).json({
error: 'SSE Inspector connection failed',
connectionId,
sessionId,
message: inspectorError instanceof Error ? inspectorError.message : 'Unknown error',
});
}
}
} else {
logger.info(`SSE Proxy connection: ${connectionId}`);
try {
await proxyHandles.handleSseProxy(req, res, `${config.app.apiPrefix}/webmcp/messages`);
} catch (proxyError) {
logger.error(`SSE Proxy error for connection ${connectionId}:`, proxyError);
if (!res.headersSent) {
res.status(500).json({
error: 'SSE Proxy connection failed',
connectionId,
message: proxyError instanceof Error ? proxyError.message : 'Unknown error',
});
}
}
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Agent Server SSE error for connection ${connectionId}:`, error);
if (!res.headersSent) {
res.status(500).json({
error: 'SSE connection failed',
connectionId,
duration: `${duration}ms`,
message: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
});
}
}
});
router.post('/messages', async (req: Request, res: Response) => {
const startTime = Date.now();
const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
try {
await proxyHandles.handleSseMessage(req, res);
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Agent Server message error for ${messageId}:`, error);
let errorCode = 'MESSAGE_ERROR';
let statusCode = 500;
let errorMessage = 'Message processing failed';
if (error instanceof Error) {
if (error.message.includes('session') || error.message.includes('not found')) {
errorCode = 'SESSION_ERROR';
statusCode = 404;
errorMessage = '会话未找到或已断开';
} else if (error.message.includes('transport') || error.message.includes('connection')) {
errorCode = 'TRANSPORT_ERROR';
statusCode = 503;
errorMessage = '传输连接错误';
} else if (error.message.includes('timeout')) {
errorCode = 'TIMEOUT_ERROR';
statusCode = 408;
errorMessage = '消息处理超时';
} else if (error.message.includes('permission') || error.message.includes('unauthorized')) {
errorCode = 'PERMISSION_ERROR';
statusCode = 403;
errorMessage = '权限不足';
}
}
if (!res.headersSent) {
res.status(statusCode).json({
error: errorCode,
message: errorMessage,
messageId,
duration: `${duration}ms`,
timestamp: new Date().toISOString(),
details:
process.env.NODE_ENV === 'development' ? (error instanceof Error ? error.stack : String(error)) : undefined,
});
}
}
});
router.all('/mcp', async (req: Request, res: Response) => {
const startTime = Date.now();
const requestId = `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
logger.debug('🚀 MCP ROUTE: Request entered router.all handler', {
requestId,
method: req.method,
url: req.url,
originalUrl: req.originalUrl,
path: req.path,
headers: {
'content-type': req.headers['content-type'],
authorization: req.headers.authorization ? 'present' : 'missing',
origin: req.headers.origin,
},
timestamp: new Date().toISOString(),
});
try {
res.setHeader('X-MCP-Request-ID', requestId);
res.setHeader('X-MCP-Method', req.method);
res.setHeader('X-Processing-Start', new Date().toISOString());
if ((req.method || '').toUpperCase() === 'GET' && req.headers.accept?.includes('text/event-stream')) {
const connectSessionId = req.headers['mcp-session-id'] as string;
const clientKey = (connectSessionId || '').trim();
const cleanupOnce = (() => {
let done = false;
return () => {
if (done) return;
done = true;
try {
logger.debug('cleanupOnce', { clientKey });
if (clientKey) {
proxyHandles?.clients[clientKey]?.transport?.close?.();
delete proxyHandles?.clients[clientKey];
}
} catch (_) {
}
};
})();
res.once('close', cleanupOnce);
res.once('error', cleanupOnce);
req.once('close', cleanupOnce);
}
let mcpPromise: Promise<any>;
if (req.query.sessionId) {
const sessionId = req.query.sessionId as string;
logger.debug('🔍 MCP ROUTE: Inspector mode', {
requestId,
sessionId,
method: req.method,
});
mcpPromise = proxyHandles.handleStreamInspector(req, res);
} else {
logger.debug('🔍 MCP ROUTE: Standard MCP request', {
requestId,
method: req.method,
});
mcpPromise = proxyHandles.handleStreamRequest(req, res);
}
try {
await mcpPromise;
const duration = Date.now() - startTime;
logger.debug('✅ MCP ROUTE: Request completed successfully', {
requestId,
duration: `${duration}ms`,
method: req.method,
});
} catch (mcpError) {
const duration = Date.now() - startTime;
logger.error('❌ MCP ROUTE: Request failed', {
requestId,
error: mcpError instanceof Error ? mcpError.message : String(mcpError),
duration: `${duration}ms`,
method: req.method,
});
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal MCP processing error',
data: {
requestId,
error: mcpError instanceof Error ? mcpError.message : String(mcpError),
timestamp: new Date().toISOString(),
},
},
});
}
}
} catch (error) {
const duration = Date.now() - startTime;
logger.error('❌ MCP ROUTE: Unexpected error in handler', {
requestId,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
duration: `${duration}ms`,
method: req.method,
});
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Unexpected error in MCP handler',
data: {
requestId,
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
},
},
});
}
}
});
router.get('/list', async (req: Request, res: Response) => {
const sessions: Record<string, object> = {};
for (const sessionId in proxyHandles.clients) {
const { user, device, type } = proxyHandles.clients[sessionId] || {};
sessions[sessionId] = { user, device, type };
}
res.json(sessions);
});
router.get('/remoter', async (req: Request, res: Response) => {
const sessions: Record<string, object> = {};
for (const sessionId in proxyHandles.remoters) {
const { user, client, device, type } = proxyHandles.remoters[sessionId] || {};
sessions[sessionId] = { user, client, device, type };
}
res.json(sessions);
});
router.get('/reset', async (req: Request, res: Response) => {
res.json(proxyHandles.reset());
});
router.get('/tools', async (req: Request, res: Response) => {
const sessionId = req.query.sessionId as string;
const client = proxyHandles.clients[sessionId]?.client;
if (client) {
const result = await client.listTools();
res.json({ result });
return;
}
res.json({ result: `No client found for session ID ${sessionId}` });
});
router.get('/client', async (req: Request, res: Response) => {
try {
let sessionId = req.query.sessionId as string;
if (!sessionId) {
return res.status(200).json({
status: 400,
error: 'MISSING_SESSION_ID',
message: 'sessionId is required',
});
}
if (sessionId.length === 6) {
try {
const suffix = sessionId;
const allSessionIds = Object.keys(proxyHandles.clients || {});
const matched = allSessionIds.find((id) => id.endsWith(suffix));
if (matched) {
sessionId = matched;
}
} catch (_) {
}
}
const entry = proxyHandles.clients[sessionId];
if (!entry) {
return res.status(200).json({
status: 404,
error: 'SESSION_NOT_FOUND',
message: `No client found for session ID ${sessionId}`,
});
}
const { user, device, type } = entry;
return res.status(200).json({ status: 0, data: { sessionId, user, device, type } });
} catch (error) {
return res.status(200).json({
status: 500,
error: 'INTERNAL_ERROR',
message: error instanceof Error ? error.message : 'Unexpected error',
});
}
});
export default router;