mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-31 06:52:04 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47d9f55dc5 | ||
|
|
5575630711 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.22.20] - 2025-11-19
|
||||
|
||||
### 🔄 Dependencies
|
||||
|
||||
**n8n Update to 1.120.3**
|
||||
|
||||
Updated all n8n-related dependencies to their latest versions:
|
||||
|
||||
- n8n: 1.119.1 → 1.120.3
|
||||
- n8n-core: 1.118.0 → 1.119.2
|
||||
- n8n-workflow: 1.116.0 → 1.117.0
|
||||
- @n8n/n8n-nodes-langchain: 1.118.0 → 1.119.1
|
||||
- Rebuilt node database with 544 nodes (439 from n8n-nodes-base, 105 from @n8n/n8n-nodes-langchain)
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.22.18] - 2025-11-14
|
||||
|
||||
### ✨ Features
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||
|
||||
@@ -1114,6 +1114,13 @@ Current database coverage (n8n v1.117.2):
|
||||
|
||||
## 🔄 Recent Updates
|
||||
|
||||
### v2.22.19 - Critical Bug Fix
|
||||
**Fixed:** Stack overflow in session removal (Issue #427)
|
||||
- Eliminated infinite recursion in HTTP server session cleanup
|
||||
- Transport resources now deleted before closing to prevent circular event handler chain
|
||||
- Production logs no longer show "RangeError: Maximum call stack size exceeded"
|
||||
- All session cleanup operations now complete successfully without crashes
|
||||
|
||||
See [CHANGELOG.md](./docs/CHANGELOG.md) for full version history and recent changes.
|
||||
|
||||
## ⚠️ Known Issues
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
1783
package-lock.json
generated
1783
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.22.18",
|
||||
"version": "2.22.20",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -140,15 +140,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.118.0",
|
||||
"@n8n/n8n-nodes-langchain": "^1.119.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"lru-cache": "^11.2.1",
|
||||
"n8n": "^1.119.1",
|
||||
"n8n-core": "^1.118.0",
|
||||
"n8n-workflow": "^1.116.0",
|
||||
"n8n": "^1.120.3",
|
||||
"n8n-core": "^1.119.2",
|
||||
"n8n-workflow": "^1.117.0",
|
||||
"openai": "^4.77.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
|
||||
@@ -155,17 +155,22 @@ export class SingleSessionHTTPServer {
|
||||
*/
|
||||
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
||||
try {
|
||||
// Close transport if exists
|
||||
if (this.transports[sessionId]) {
|
||||
await this.transports[sessionId].close();
|
||||
delete this.transports[sessionId];
|
||||
}
|
||||
|
||||
// Remove server, metadata, and context
|
||||
// Store reference to transport before deletion
|
||||
const transport = this.transports[sessionId];
|
||||
|
||||
// Delete transport FIRST to prevent onclose handler from triggering recursion
|
||||
// This breaks the circular reference: removeSession -> close -> onclose -> removeSession
|
||||
delete this.transports[sessionId];
|
||||
delete this.servers[sessionId];
|
||||
delete this.sessionMetadata[sessionId];
|
||||
delete this.sessionContexts[sessionId];
|
||||
|
||||
|
||||
// Close transport AFTER deletion
|
||||
// When onclose handler fires, it won't find the transport anymore
|
||||
if (transport) {
|
||||
await transport.close();
|
||||
}
|
||||
|
||||
logger.info('Session removed', { sessionId, reason });
|
||||
} catch (error) {
|
||||
logger.warn('Error removing session', { sessionId, reason, error });
|
||||
|
||||
@@ -411,17 +411,17 @@ describe('HTTP Server Session Management', () => {
|
||||
|
||||
it('should handle removeSession with transport close error gracefully', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const mockTransport = {
|
||||
|
||||
const mockTransport = {
|
||||
close: vi.fn().mockRejectedValue(new Error('Transport close failed'))
|
||||
};
|
||||
(server as any).transports = { 'test-session': mockTransport };
|
||||
(server as any).servers = { 'test-session': {} };
|
||||
(server as any).sessionMetadata = {
|
||||
'test-session': {
|
||||
(server as any).sessionMetadata = {
|
||||
'test-session': {
|
||||
lastAccess: new Date(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Should not throw even if transport close fails
|
||||
@@ -429,11 +429,67 @@ describe('HTTP Server Session Management', () => {
|
||||
|
||||
// Verify transport close was attempted
|
||||
expect(mockTransport.close).toHaveBeenCalled();
|
||||
|
||||
|
||||
// Session should still be cleaned up despite transport error
|
||||
// Note: The actual implementation may handle errors differently, so let's verify what we can
|
||||
expect(mockTransport.close).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should not cause infinite recursion when transport.close triggers onclose handler', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const sessionId = 'test-recursion-session';
|
||||
let closeCallCount = 0;
|
||||
let oncloseCallCount = 0;
|
||||
|
||||
// Create a mock transport that simulates the actual behavior
|
||||
const mockTransport = {
|
||||
close: vi.fn().mockImplementation(async () => {
|
||||
closeCallCount++;
|
||||
// Simulate the actual SDK behavior: close() triggers onclose handler
|
||||
if (mockTransport.onclose) {
|
||||
oncloseCallCount++;
|
||||
await mockTransport.onclose();
|
||||
}
|
||||
}),
|
||||
onclose: null as (() => Promise<void>) | null,
|
||||
sessionId
|
||||
};
|
||||
|
||||
// Set up the transport and session data
|
||||
(server as any).transports = { [sessionId]: mockTransport };
|
||||
(server as any).servers = { [sessionId]: {} };
|
||||
(server as any).sessionMetadata = {
|
||||
[sessionId]: {
|
||||
lastAccess: new Date(),
|
||||
createdAt: new Date()
|
||||
}
|
||||
};
|
||||
|
||||
// Set up onclose handler like the real implementation does
|
||||
// This handler calls removeSession, which could cause infinite recursion
|
||||
mockTransport.onclose = async () => {
|
||||
await (server as any).removeSession(sessionId, 'transport_closed');
|
||||
};
|
||||
|
||||
// Call removeSession - this should NOT cause infinite recursion
|
||||
await (server as any).removeSession(sessionId, 'manual_removal');
|
||||
|
||||
// Verify the fix works:
|
||||
// 1. close() should be called exactly once
|
||||
expect(closeCallCount).toBe(1);
|
||||
|
||||
// 2. onclose handler should be triggered
|
||||
expect(oncloseCallCount).toBe(1);
|
||||
|
||||
// 3. Transport should be deleted and not cause second close attempt
|
||||
expect((server as any).transports[sessionId]).toBeUndefined();
|
||||
expect((server as any).servers[sessionId]).toBeUndefined();
|
||||
expect((server as any).sessionMetadata[sessionId]).toBeUndefined();
|
||||
|
||||
// 4. If there was a recursion bug, closeCallCount would be > 1
|
||||
// or the test would timeout/crash with "Maximum call stack size exceeded"
|
||||
});
|
||||
});
|
||||
|
||||
describe('Session Metadata Tracking', () => {
|
||||
|
||||
Reference in New Issue
Block a user