first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,37 @@
import type { Tool } from '@langchain/core/tools';
import type { SessionStore } from './SessionStore';
export class InMemorySessionStore implements SessionStore {
private sessions = new Set<string>();
private tools: Record<string, Tool[]> = {};
// eslint-disable-next-line @typescript-eslint/require-await
async register(sessionId: string): Promise<void> {
this.sessions.add(sessionId);
}
// eslint-disable-next-line @typescript-eslint/require-await
async validate(sessionId: string): Promise<boolean> {
return this.sessions.has(sessionId);
}
// eslint-disable-next-line @typescript-eslint/require-await
async unregister(sessionId: string): Promise<void> {
this.sessions.delete(sessionId);
delete this.tools[sessionId];
}
getTools(sessionId: string): Tool[] | undefined {
return this.tools[sessionId];
}
setTools(sessionId: string, tools: Tool[]): void {
this.tools[sessionId] = tools;
}
clearTools(sessionId: string): void {
delete this.tools[sessionId];
}
}
@@ -0,0 +1,45 @@
import type { Tool } from '@langchain/core/tools';
import type { SessionStore } from './SessionStore';
export interface RedisPublisher {
set(key: string, value: string, ttl: number): Promise<void>;
get(key: string): Promise<string | null>;
clear(key: string): Promise<void>;
}
export class RedisSessionStore implements SessionStore {
private tools: Record<string, Tool[]> = {};
constructor(
private publisher: RedisPublisher,
private getSessionKey: (sessionId: string) => string,
private ttl: number,
) {}
async register(sessionId: string): Promise<void> {
await this.publisher.set(this.getSessionKey(sessionId), '1', this.ttl);
}
async validate(sessionId: string): Promise<boolean> {
const result = await this.publisher.get(this.getSessionKey(sessionId));
return result !== null;
}
async unregister(sessionId: string): Promise<void> {
await this.publisher.clear(this.getSessionKey(sessionId));
delete this.tools[sessionId];
}
getTools(sessionId: string): Tool[] | undefined {
return this.tools[sessionId];
}
setTools(sessionId: string, tools: Tool[]): void {
this.tools[sessionId] = tools;
}
clearTools(sessionId: string): void {
delete this.tools[sessionId];
}
}
@@ -0,0 +1,68 @@
import type { Tool } from '@langchain/core/tools';
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { SessionStore } from './SessionStore';
import type { McpTransport } from '../transport/Transport';
export interface SessionInfo {
sessionId: string;
server: Server;
transport: McpTransport;
}
export class SessionManager {
private sessions: Record<string, SessionInfo> = {};
constructor(private store: SessionStore) {}
async registerSession(
sessionId: string,
server: Server,
transport: McpTransport,
tools?: Tool[],
): Promise<void> {
if (!sessionId) return;
await this.store.register(sessionId);
this.sessions[sessionId] = { sessionId, server, transport };
if (tools) {
this.store.setTools(sessionId, tools);
}
}
async destroySession(sessionId: string): Promise<void> {
await this.store.unregister(sessionId);
delete this.sessions[sessionId];
}
getSession(sessionId: string): SessionInfo | undefined {
return this.sessions[sessionId];
}
getTransport(sessionId: string): McpTransport | undefined {
return this.sessions[sessionId]?.transport;
}
getServer(sessionId: string): Server | undefined {
return this.sessions[sessionId]?.server;
}
async isSessionValid(sessionId: string): Promise<boolean> {
return await this.store.validate(sessionId);
}
getTools(sessionId: string): Tool[] | undefined {
return this.store.getTools(sessionId);
}
setTools(sessionId: string, tools: Tool[]): void {
this.store.setTools(sessionId, tools);
}
setStore(store: SessionStore): void {
this.store = store;
}
getStore(): SessionStore {
return this.store;
}
}
@@ -0,0 +1,10 @@
import type { Tool } from '@langchain/core/tools';
export interface SessionStore {
register(sessionId: string): Promise<void>;
validate(sessionId: string): Promise<boolean>;
unregister(sessionId: string): Promise<void>;
getTools(sessionId: string): Tool[] | undefined;
setTools(sessionId: string, tools: Tool[]): void;
clearTools(sessionId: string): void;
}
@@ -0,0 +1,132 @@
import { createMockTool } from '../../__tests__/helpers';
import { InMemorySessionStore } from '../InMemorySessionStore';
describe('InMemorySessionStore', () => {
let store: InMemorySessionStore;
beforeEach(() => {
store = new InMemorySessionStore();
});
describe('session lifecycle', () => {
it('should register and validate a session', async () => {
await store.register('session-1');
expect(await store.validate('session-1')).toBe(true);
});
it('should return false for unregistered session', async () => {
expect(await store.validate('non-existent')).toBe(false);
});
it('should handle registering same session twice (idempotent)', async () => {
await store.register('session-1');
await store.register('session-1');
expect(await store.validate('session-1')).toBe(true);
});
it('should unregister session and invalidate it', async () => {
await store.register('session-1');
await store.unregister('session-1');
expect(await store.validate('session-1')).toBe(false);
});
it('should handle unregistering non-existent session gracefully', async () => {
await expect(store.unregister('non-existent')).resolves.not.toThrow();
});
it('should handle multiple sessions independently', async () => {
await store.register('session-1');
await store.register('session-2');
await store.register('session-3');
expect(await store.validate('session-1')).toBe(true);
expect(await store.validate('session-2')).toBe(true);
expect(await store.validate('session-3')).toBe(true);
await store.unregister('session-2');
expect(await store.validate('session-1')).toBe(true);
expect(await store.validate('session-2')).toBe(false);
expect(await store.validate('session-3')).toBe(true);
});
it('should handle empty string as session id', async () => {
await store.register('');
expect(await store.validate('')).toBe(true);
});
});
describe('tools management', () => {
const mockTools = [createMockTool('tool-1'), createMockTool('tool-2')];
it('should set and get tools for a session', () => {
store.setTools('session-1', mockTools);
expect(store.getTools('session-1')).toEqual(mockTools);
});
it('should return undefined for session without tools', () => {
expect(store.getTools('session-without-tools')).toBeUndefined();
});
it('should clear tools for a session', () => {
store.setTools('session-1', mockTools);
store.clearTools('session-1');
expect(store.getTools('session-1')).toBeUndefined();
});
it('should clear tools when session is unregistered', async () => {
await store.register('session-1');
store.setTools('session-1', mockTools);
await store.unregister('session-1');
expect(store.getTools('session-1')).toBeUndefined();
});
it('should handle clearing tools for non-existent session', () => {
expect(() => store.clearTools('non-existent')).not.toThrow();
});
it('should isolate tools between sessions', () => {
const tools1 = [createMockTool('tool-a')];
const tools2 = [createMockTool('tool-b')];
store.setTools('session-1', tools1);
store.setTools('session-2', tools2);
expect(store.getTools('session-1')).toEqual(tools1);
expect(store.getTools('session-2')).toEqual(tools2);
});
it('should overwrite tools when set again', () => {
const tools1 = [createMockTool('tool-a')];
const tools2 = [createMockTool('tool-b'), createMockTool('tool-c')];
store.setTools('session-1', tools1);
store.setTools('session-1', tools2);
expect(store.getTools('session-1')).toEqual(tools2);
});
it('should handle setting empty tools array', () => {
store.setTools('session-1', []);
expect(store.getTools('session-1')).toEqual([]);
});
it('should not affect tools when clearing non-existent session', () => {
const tools = [createMockTool('tool-a')];
store.setTools('session-1', tools);
store.clearTools('session-2');
expect(store.getTools('session-1')).toEqual(tools);
});
});
describe('combined session and tools operations', () => {
it('should allow setting tools before registering session', () => {
const tools = [createMockTool('tool-1')];
store.setTools('session-1', tools);
expect(store.getTools('session-1')).toEqual(tools);
});
it('should not delete tools when registering session with existing tools', async () => {
const tools = [createMockTool('tool-1')];
store.setTools('session-1', tools);
await store.register('session-1');
expect(store.getTools('session-1')).toEqual(tools);
});
});
});
@@ -0,0 +1,107 @@
import type { Tool } from '@langchain/core/tools';
import { mock } from 'jest-mock-extended';
import { RedisSessionStore, type RedisPublisher } from '../RedisSessionStore';
describe('RedisSessionStore', () => {
let store: RedisSessionStore;
let mockPublisher: jest.Mocked<RedisPublisher>;
const getSessionKey = (sessionId: string) => `mcp-session:${sessionId}`;
const ttl = 3600;
beforeEach(() => {
mockPublisher = {
set: jest.fn().mockResolvedValue(undefined),
get: jest.fn().mockResolvedValue(null),
clear: jest.fn().mockResolvedValue(undefined),
};
store = new RedisSessionStore(mockPublisher, getSessionKey, ttl);
});
describe('register', () => {
it('should store session with TTL in Redis', async () => {
await store.register('session-123');
expect(mockPublisher.set).toHaveBeenCalledWith('mcp-session:session-123', '1', ttl);
});
});
describe('validate', () => {
it('should return true when session exists in Redis', async () => {
mockPublisher.get.mockResolvedValue('1');
const result = await store.validate('session-123');
expect(mockPublisher.get).toHaveBeenCalledWith('mcp-session:session-123');
expect(result).toBe(true);
});
it('should return false when session does not exist', async () => {
mockPublisher.get.mockResolvedValue(null);
const result = await store.validate('non-existent');
expect(result).toBe(false);
});
});
describe('unregister', () => {
it('should clear session from Redis and remove tools', async () => {
const mockTool = mock<Tool>();
store.setTools('session-123', [mockTool]);
await store.unregister('session-123');
expect(mockPublisher.clear).toHaveBeenCalledWith('mcp-session:session-123');
expect(store.getTools('session-123')).toBeUndefined();
});
});
describe('tool management', () => {
it('should store and retrieve tools', () => {
const mockTool1 = mock<Tool>();
const mockTool2 = mock<Tool>();
store.setTools('session-1', [mockTool1]);
store.setTools('session-2', [mockTool2]);
expect(store.getTools('session-1')).toEqual([mockTool1]);
expect(store.getTools('session-2')).toEqual([mockTool2]);
});
it('should return undefined for unknown session', () => {
expect(store.getTools('unknown')).toBeUndefined();
});
it('should clear tools for a session', () => {
const mockTool = mock<Tool>();
store.setTools('session-123', [mockTool]);
store.clearTools('session-123');
expect(store.getTools('session-123')).toBeUndefined();
});
});
describe('custom key function', () => {
it('should use custom key function for Redis operations', async () => {
const customKeyFn = (sessionId: string) => `custom:prefix:${sessionId}`;
const customStore = new RedisSessionStore(mockPublisher, customKeyFn, ttl);
await customStore.register('test-id');
expect(mockPublisher.set).toHaveBeenCalledWith('custom:prefix:test-id', '1', ttl);
});
});
describe('custom TTL', () => {
it('should use custom TTL for register', async () => {
const customTtl = 86400;
const customStore = new RedisSessionStore(mockPublisher, getSessionKey, customTtl);
await customStore.register('test-id');
expect(mockPublisher.set).toHaveBeenCalledWith(expect.any(String), '1', customTtl);
});
});
});
@@ -0,0 +1,186 @@
import { createMockServer, createMockTransport, createMockTool } from '../../__tests__/helpers';
import { SessionManager } from '../SessionManager';
import type { SessionStore } from '../SessionStore';
describe('SessionManager', () => {
let manager: SessionManager;
let mockStore: jest.Mocked<SessionStore>;
beforeEach(() => {
mockStore = {
register: jest.fn().mockResolvedValue(undefined),
validate: jest.fn().mockResolvedValue(true),
unregister: jest.fn().mockResolvedValue(undefined),
getTools: jest.fn(),
setTools: jest.fn(),
clearTools: jest.fn(),
};
manager = new SessionManager(mockStore);
});
describe('registerSession', () => {
it('should register session with server and transport', async () => {
const server = createMockServer();
const transport = createMockTransport('session-1');
await manager.registerSession('session-1', server, transport);
expect(mockStore.register).toHaveBeenCalledWith('session-1');
expect(manager.getSession('session-1')).toEqual({
sessionId: 'session-1',
server,
transport,
});
});
it('should store tools when provided', async () => {
const tools = [createMockTool('tool-1')];
await manager.registerSession(
'session-1',
createMockServer(),
createMockTransport('session-1'),
tools,
);
expect(mockStore.setTools).toHaveBeenCalledWith('session-1', tools);
});
it('should not call setTools when no tools provided', async () => {
await manager.registerSession(
'session-1',
createMockServer(),
createMockTransport('session-1'),
);
expect(mockStore.setTools).not.toHaveBeenCalled();
});
it('should not register if sessionId is empty', async () => {
await manager.registerSession('', createMockServer(), createMockTransport(''));
expect(mockStore.register).not.toHaveBeenCalled();
expect(manager.getSession('')).toBeUndefined();
});
it('should overwrite existing session when registering same sessionId', async () => {
const server1 = createMockServer();
const transport1 = createMockTransport('session-1');
const server2 = createMockServer();
const transport2 = createMockTransport('session-1');
await manager.registerSession('session-1', server1, transport1);
await manager.registerSession('session-1', server2, transport2);
const session = manager.getSession('session-1');
expect(session?.server).toBe(server2);
expect(session?.transport).toBe(transport2);
});
});
describe('destroySession', () => {
it('should remove session and delegate to store', async () => {
await manager.registerSession(
'session-1',
createMockServer(),
createMockTransport('session-1'),
);
await manager.destroySession('session-1');
expect(mockStore.unregister).toHaveBeenCalledWith('session-1');
expect(manager.getSession('session-1')).toBeUndefined();
});
it('should handle destroying non-existent session', async () => {
await expect(manager.destroySession('non-existent')).resolves.not.toThrow();
expect(mockStore.unregister).toHaveBeenCalledWith('non-existent');
});
});
describe('getSession', () => {
it('should return session info for registered session', async () => {
const server = createMockServer();
const transport = createMockTransport('session-1');
await manager.registerSession('session-1', server, transport);
const session = manager.getSession('session-1');
expect(session).toEqual({
sessionId: 'session-1',
server,
transport,
});
});
it('should return undefined for unregistered session', () => {
expect(manager.getSession('non-existent')).toBeUndefined();
});
});
describe('getTransport', () => {
it('should return transport for registered session', async () => {
const transport = createMockTransport('session-1');
await manager.registerSession('session-1', createMockServer(), transport);
expect(manager.getTransport('session-1')).toBe(transport);
});
it('should return undefined for unregistered session', () => {
expect(manager.getTransport('non-existent')).toBeUndefined();
});
});
describe('getServer', () => {
it('should return server for registered session', async () => {
const server = createMockServer();
await manager.registerSession('session-1', server, createMockTransport('session-1'));
expect(manager.getServer('session-1')).toBe(server);
});
it('should return undefined for unregistered session', () => {
expect(manager.getServer('non-existent')).toBeUndefined();
});
});
describe('isSessionValid', () => {
it('should delegate to store.validate and return true', async () => {
mockStore.validate.mockResolvedValue(true);
expect(await manager.isSessionValid('session-1')).toBe(true);
expect(mockStore.validate).toHaveBeenCalledWith('session-1');
});
it('should delegate to store.validate and return false', async () => {
mockStore.validate.mockResolvedValue(false);
expect(await manager.isSessionValid('session-1')).toBe(false);
});
});
describe('tools management', () => {
it('should delegate getTools to store', () => {
const tools = [createMockTool('tool-1')];
mockStore.getTools.mockReturnValue(tools);
expect(manager.getTools('session-1')).toBe(tools);
expect(mockStore.getTools).toHaveBeenCalledWith('session-1');
});
it('should delegate setTools to store', () => {
const tools = [createMockTool('tool-1')];
manager.setTools('session-1', tools);
expect(mockStore.setTools).toHaveBeenCalledWith('session-1', tools);
});
});
describe('store management', () => {
it('should allow swapping session store', () => {
const newStore = { ...mockStore } as jest.Mocked<SessionStore>;
manager.setStore(newStore);
expect(manager.getStore()).toBe(newStore);
});
it('should return current store', () => {
expect(manager.getStore()).toBe(mockStore);
});
});
});
@@ -0,0 +1,4 @@
export type { SessionStore } from './SessionStore';
export * from './InMemorySessionStore';
export * from './RedisSessionStore';
export * from './SessionManager';