
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@mcp-layer/manager
Advanced tools
Session manager for building auth-aware MCP session pools with TTL/LRU eviction.
Reusable MCP session manager with identity-based session reuse, TTL expiration, and LRU eviction.
This package is transport-agnostic. It can be used with REST plugins, CLIs, job workers, or any runtime that needs controlled MCP session lifecycles.
pnpm add @mcp-layer/manager
# or
npm install @mcp-layer/manager
# or
yarn add @mcp-layer/manager
@mcp-layer/manager solves session lifecycle concerns that are separate from any single framework:
ttl) for stale sessions.max) using least-recently-used policy.close().stats().createManager(options)Creates a manager instance.
createManager(options: {
max?: number;
ttl?: number;
sharedKey?: string;
auth?: {
mode?: 'optional' | 'required' | 'disabled';
header?: string;
scheme?: 'bearer' | 'basic' | 'raw';
};
identify?: (request) =>
| string
| {
key: string;
auth?: { scheme?: 'bearer' | 'basic' | 'raw'; token?: string; header?: string };
shared?: boolean;
};
factory: (ctx: {
identity: {
key: string;
auth: { scheme: 'bearer' | 'basic' | 'raw'; token: string; header: string } | null;
shared: boolean;
};
request: FastifyRequest;
}) => Promise<Session>;
now?: () => number;
}) => {
get(request): Promise<Session>;
stats(): {
size: number;
max: number;
ttl: number;
evictions: number;
hits: number;
misses: number;
keys: string[];
};
close(): Promise<void>;
}
options fields| Field | Type | Default | Required | Behavior |
|---|---|---|---|---|
max | number | 10 | no | Maximum cached sessions. When exceeded, oldest LRU session is evicted and closed. |
ttl | number | 300000 | no | Idle timeout in milliseconds. Expired sessions are closed and recreated on next access. |
sharedKey | string | "shared" | no | Identity key used when auth is optional and missing (or disabled). |
auth.mode | 'optional' | 'required' | 'disabled' | 'optional' | no | Controls whether identity must come from auth headers. |
auth.header | string | 'authorization' | no | Header name used for auth parsing. Case-insensitive. |
auth.scheme | 'bearer' | 'basic' | 'raw' | 'bearer' | no | Header parsing strategy when identify is not provided. |
identify | function | undefined | no | Custom identity derivation. Overrides built-in auth parsing. |
factory | function | none | yes | Creates a Session for an identity. Must return @mcp-layer/session Session. |
now | function | Date.now | no | Clock source, mainly for deterministic tests. |
| Method | Signature | Behavior |
|---|---|---|
get | get(request) => Promise<Session> | Resolves identity, returns cached session when possible, otherwise creates and caches a new session. |
stats | stats() => { size, max, ttl, evictions, hits, misses, keys } | Returns in-memory pool statistics for observability and testing. |
close | close() => Promise<void> | Closes all tracked sessions and clears in-memory state. |
Manager runtime errors are thrown as LayerError from @mcp-layer/error.
reference id.docs URL to this package README error section.When identify is not supplied, identity is derived from configured auth settings:
auth.mode = 'disabled': always uses sharedKey.auth.mode = 'optional': uses auth header when present, otherwise sharedKey.auth.mode = 'required': missing header raises a documented LayerError from identity.Scheme handling:
bearer: expects Authorization: Bearer <token>.basic: expects Authorization: Basic <base64>.raw: takes the full header value as token.This example shows the default identity path (Authorization header) with per-identity session creation. This matters when multiple callers should not always share one MCP session.
Expected behavior: requests with the same bearer token reuse one session; a different token creates a different session.
import { createManager } from '@mcp-layer/manager';
import { connect } from '@mcp-layer/connect';
import { load } from '@mcp-layer/config';
const config = await load();
const manager = createManager({
max: 10,
ttl: 5 * 60 * 1000,
factory: async function factory(ctx) {
const entry = config.get('server-name');
if (!entry) {
throw new Error('Server not found.');
}
const token = ctx.identity.auth ? ctx.identity.auth.token : undefined;
return connect(config, entry.name, {
env: token ? { MCP_AUTH_TOKEN: token } : undefined
});
}
});
This example shows tenant-based routing without forcing header auth parsing. This matters when identity comes from app-level metadata instead of authorization headers.
Expected behavior: requests with the same tenant key share one session; requests without tenant fallback to shared identity.
const manager = createManager({
identify: function identify(request) {
const tenant = request.headers['x-tenant-id'];
if (!tenant || typeof tenant !== 'string') {
return 'shared';
}
return {
key: `tenant:${tenant}`,
shared: false
};
},
factory: async function factory(ctx) {
return connect(config, 'tenant-server');
}
});
This example shows how to pass the manager into another package that resolves sessions per request.
Expected behavior: the host plugin calls manager.get(request) internally and reuses or creates sessions according to manager policy.
app.register(mcpRest, {
session,
manager
});
Call close() during process shutdown so cached sessions are closed cleanly.
await manager.close();
This section is written for high-pressure debugging moments. Each entry maps to a specific createManager(...) validation or identity-resolution branch.
Thrown from: get
This happens when your factory(ctx) returns something other than @mcp-layer/session Session. Manager cache/storage and route integration require real Session instances.
Step-by-step resolution:
factory(ctx) and verify its constructor/type.connect(...) or attach(...) rather than returning raw clients.const manager = createManager({
factory: async function makeSession() {
return connect(config, 'local-dev');
}
});
const session = await manager.get(request);
Thrown from: identity
This happens when auth.mode is set to required and the configured auth header is missing from the incoming request.
Step-by-step resolution:
mode, header, scheme) used at runtime.required.await fastify.inject({
method: 'GET',
url: '/v1/tools/weather.get',
headers: { authorization: 'Bearer test-token' }
});
Thrown from: identity
This happens when manager auth scheme is configured as basic, but the request header is not formatted as Basic <base64>.
Step-by-step resolution:
scheme: "basic" intentionally.Basic .username:password) before sending.const credentials = Buffer.from('user:pass').toString('base64');
await fastify.inject({
method: 'GET',
url: '/v1/tools/example',
headers: { authorization: `Basic ${credentials}` }
});
Thrown from: identity
This happens when manager auth scheme is bearer, but the header does not match Bearer <token>.
Step-by-step resolution:
scheme: "bearer".Bearer <token> exactly.await fastify.inject({
method: 'GET',
url: '/v1/tools/example',
headers: { authorization: 'Bearer abc123' }
});
Thrown from: identity
This happens when a custom identify(request) hook returns an unsupported shape (for example undefined, number, or object missing key).
Step-by-step resolution:
identify implementation return type.{ key, auth?, shared? }.token under auth.const manager = createManager({
identify: function identify(request) {
const tenant = String(request.headers['x-tenant-id'] ?? 'shared');
return { key: `tenant:${tenant}`, shared: false };
},
factory: makeSession
});
Thrown from: normalize
This happens when createManager receives max <= 0, NaN, or non-finite values. max controls cache capacity and must be a positive number.
Step-by-step resolution:
max (env/config flags).0, -1, NaN) and valid values.const max = Number(process.env.MCP_SESSION_MAX ?? 10);
if (!Number.isFinite(max) || max <= 0)
throw new Error('MCP_SESSION_MAX must be a positive number.');
const manager = createManager({ max, ttl: 300000, factory: makeSession });
Thrown from: normalize
This happens when createManager(...) is called with undefined, null, or a non-object value. Manager initialization requires an options object.
Step-by-step resolution:
undefined.createManager.const manager = createManager({
factory: makeSession,
max: 10,
ttl: 300000
});
Thrown from: normalize
This happens when manager options do not include a callable factory. Session creation is delegated entirely to this function.
Step-by-step resolution:
factory exists and is a function.Session.const manager = createManager({
factory: async function makeSession(ctx) {
return connect(config, ctx.identity.key);
}
});
Thrown from: normalize
This happens when ttl is <= 0, NaN, or non-finite. Session entries use ttl for eviction; invalid values break expiration semantics.
Step-by-step resolution:
ttl > 0.const ttl = Number(process.env.MCP_SESSION_TTL_MS ?? 300000);
if (!Number.isFinite(ttl) || ttl <= 0)
throw new Error('MCP_SESSION_TTL_MS must be a positive number.');
const manager = createManager({ factory: makeSession, max: 10, ttl });
MIT
FAQs
Session manager for building auth-aware MCP session pools with TTL/LRU eviction.
We found that @mcp-layer/manager demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.