fix: implement Single-Session architecture to resolve MCP stream errors
- Add ConsoleManager to prevent console output interference with StreamableHTTPServerTransport - Implement SingleSessionHTTPServer with persistent session reuse - Create N8NMCPEngine for clean service integration - Add automatic session expiry after 30 minutes of inactivity - Update logger to be HTTP-aware during active requests - Maintain backward compatibility with existing deployments This fixes the "stream is not readable" error by implementing the Hybrid Single-Session architecture as documented in MCP_ERROR_FIX_PLAN.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
83
src/utils/console-manager.ts
Normal file
83
src/utils/console-manager.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Console Manager for MCP HTTP Server
|
||||
*
|
||||
* Prevents console output from interfering with StreamableHTTPServerTransport
|
||||
* by silencing console methods during MCP request handling.
|
||||
*/
|
||||
export class ConsoleManager {
|
||||
private originalConsole = {
|
||||
log: console.log,
|
||||
error: console.error,
|
||||
warn: console.warn,
|
||||
info: console.info,
|
||||
debug: console.debug,
|
||||
trace: console.trace
|
||||
};
|
||||
|
||||
private isSilenced = false;
|
||||
|
||||
/**
|
||||
* Silence all console output
|
||||
*/
|
||||
public silence(): void {
|
||||
if (this.isSilenced || process.env.MCP_MODE !== 'http') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSilenced = true;
|
||||
process.env.MCP_REQUEST_ACTIVE = 'true';
|
||||
console.log = () => {};
|
||||
console.error = () => {};
|
||||
console.warn = () => {};
|
||||
console.info = () => {};
|
||||
console.debug = () => {};
|
||||
console.trace = () => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore original console methods
|
||||
*/
|
||||
public restore(): void {
|
||||
if (!this.isSilenced) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSilenced = false;
|
||||
process.env.MCP_REQUEST_ACTIVE = 'false';
|
||||
console.log = this.originalConsole.log;
|
||||
console.error = this.originalConsole.error;
|
||||
console.warn = this.originalConsole.warn;
|
||||
console.info = this.originalConsole.info;
|
||||
console.debug = this.originalConsole.debug;
|
||||
console.trace = this.originalConsole.trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an operation with console silencing
|
||||
* Automatically restores console on completion or error
|
||||
*/
|
||||
public async wrapOperation<T>(operation: () => T | Promise<T>): Promise<T> {
|
||||
this.silence();
|
||||
try {
|
||||
const result = operation();
|
||||
if (result instanceof Promise) {
|
||||
return await result.finally(() => this.restore());
|
||||
}
|
||||
this.restore();
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.restore();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if console is currently silenced
|
||||
*/
|
||||
public get isActive(): boolean {
|
||||
return this.isSilenced;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance for easy use
|
||||
export const consoleManager = new ConsoleManager();
|
||||
@@ -14,6 +14,8 @@ export interface LoggerConfig {
|
||||
export class Logger {
|
||||
private config: LoggerConfig;
|
||||
private static instance: Logger;
|
||||
private useFileLogging = false;
|
||||
private fileStream: any = null;
|
||||
|
||||
constructor(config?: Partial<LoggerConfig>) {
|
||||
this.config = {
|
||||
@@ -52,6 +54,13 @@ export class Logger {
|
||||
if (level <= this.config.level) {
|
||||
const formattedMessage = this.formatMessage(levelName, message);
|
||||
|
||||
// In HTTP mode during request handling, suppress console output
|
||||
// The ConsoleManager will handle this, but we add a safety check
|
||||
if (process.env.MCP_MODE === 'http' && process.env.MCP_REQUEST_ACTIVE === 'true') {
|
||||
// Silently drop the log during active MCP requests
|
||||
return;
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case LogLevel.ERROR:
|
||||
console.error(formattedMessage, ...args);
|
||||
|
||||
Reference in New Issue
Block a user