New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@mcp-layer/manager

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mcp-layer/manager

Session manager for building auth-aware MCP session pools with TTL/LRU eviction.

latest
Source
npmnpm
Version
0.2.1
Version published
Maintainers
1
Created
Source

@mcp-layer/manager

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.

Table of Contents

  • Installation
  • What This Package Provides
  • API Reference
  • Identity Rules
  • Example: Default Auth Parsing
  • Example: Custom Identity Strategy
  • Example: Integration with a Plugin
  • Shutdown
  • Runtime Error Reference

Installation

pnpm add @mcp-layer/manager
# or
npm install @mcp-layer/manager
# or
yarn add @mcp-layer/manager

What This Package Provides

@mcp-layer/manager solves session lifecycle concerns that are separate from any single framework:

  • Identity derivation per incoming request/context.
  • Session reuse for repeated identities.
  • Time-based eviction (ttl) for stale sessions.
  • Capacity-based eviction (max) using least-recently-used policy.
  • Graceful shutdown via close().
  • Runtime visibility via stats().

API Reference

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

FieldTypeDefaultRequiredBehavior
maxnumber10noMaximum cached sessions. When exceeded, oldest LRU session is evicted and closed.
ttlnumber300000noIdle timeout in milliseconds. Expired sessions are closed and recreated on next access.
sharedKeystring"shared"noIdentity key used when auth is optional and missing (or disabled).
auth.mode'optional' | 'required' | 'disabled''optional'noControls whether identity must come from auth headers.
auth.headerstring'authorization'noHeader name used for auth parsing. Case-insensitive.
auth.scheme'bearer' | 'basic' | 'raw''bearer'noHeader parsing strategy when identify is not provided.
identifyfunctionundefinednoCustom identity derivation. Overrides built-in auth parsing.
factoryfunctionnoneyesCreates a Session for an identity. Must return @mcp-layer/session Session.
nowfunctionDate.nownoClock source, mainly for deterministic tests.

Manager methods

MethodSignatureBehavior
getget(request) => Promise<Session>Resolves identity, returns cached session when possible, otherwise creates and caches a new session.
statsstats() => { size, max, ttl, evictions, hits, misses, keys }Returns in-memory pool statistics for observability and testing.
closeclose() => Promise<void>Closes all tracked sessions and clears in-memory state.

Error Behavior

Manager runtime errors are thrown as LayerError from @mcp-layer/error.

  • Every error includes package + method source metadata.
  • Every error includes a stable reference id.
  • Every error includes a generated docs URL to this package README error section.
  • Runtime references and full debugging playbooks are documented in Runtime Error Reference.

Identity Rules

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.

Example: Default Auth Parsing

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
    });
  }
});

Example: Custom Identity Strategy

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');
  }
});

Example: Integration with a Plugin

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
});

Shutdown

Call close() during process shutdown so cached sessions are closed cleanly.

await manager.close();

Runtime Error Reference

This section is written for high-pressure debugging moments. Each entry maps to a specific createManager(...) validation or identity-resolution branch.

factory must return a Session instance.

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:

  • Inspect the return value of factory(ctx) and verify its constructor/type.
  • Ensure the factory awaits connect(...) or attach(...) rather than returning raw clients.
  • Reject non-Session returns in your own factory wrapper.
  • Add tests for one invalid factory return and one valid Session return.
Fix Example: return Session objects from manager factory
const manager = createManager({
  factory: async function makeSession() {
    return connect(config, 'local-dev');
  }
});

const session = await manager.get(request);

Authorization header is required.

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:

  • Confirm manager auth config (mode, header, scheme) used at runtime.
  • Check upstream proxy/gateway forwarding for the authorization header.
  • Ensure requests include the required header when manager auth is required.
  • Add tests for missing-header rejection and valid-header acceptance.
Fix Example: send required auth header for manager identity
await fastify.inject({
  method: 'GET',
  url: '/v1/tools/weather.get',
  headers: { authorization: 'Bearer test-token' }
});

Authorization header must use Basic scheme.

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:

  • Verify manager auth config uses scheme: "basic" intentionally.
  • Check header format and ensure it starts with Basic .
  • Encode credentials as Base64 (username:password) before sending.
  • Add tests for incorrect scheme prefix and valid Basic header parsing.
Fix Example: send correctly formatted Basic auth header
const credentials = Buffer.from('user:pass').toString('base64');
await fastify.inject({
  method: 'GET',
  url: '/v1/tools/example',
  headers: { authorization: `Basic ${credentials}` }
});

Authorization header must use Bearer scheme.

Thrown from: identity

This happens when manager auth scheme is bearer, but the header does not match Bearer <token>.

Step-by-step resolution:

  • Confirm manager config uses scheme: "bearer".
  • Check clients/proxies are not rewriting the header prefix.
  • Ensure token is sent as Bearer <token> exactly.
  • Add tests for malformed bearer headers and valid token headers.
Fix Example: send Bearer token with correct prefix
await fastify.inject({
  method: 'GET',
  url: '/v1/tools/example',
  headers: { authorization: 'Bearer abc123' }
});

identify() must return a string or { key, auth } object.

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:

  • Review your custom identify implementation return type.
  • Return either a string key or { key, auth?, shared? }.
  • If supplying auth metadata, include token under auth.
  • Add tests for both supported return shapes.
Fix Example: implement identify with a supported return shape
const manager = createManager({
  identify: function identify(request) {
    const tenant = String(request.headers['x-tenant-id'] ?? 'shared');
    return { key: `tenant:${tenant}`, shared: false };
  },
  factory: makeSession
});

max must be a positive number.

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:

  • Inspect the source of max (env/config flags).
  • Parse/coerce to number and validate positivity before manager creation.
  • Set a sensible upper bound for your workload to avoid churn.
  • Add tests for invalid (0, -1, NaN) and valid values.
Fix Example: validate manager max before createManager
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 });

Session manager options are required.

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:

  • Check the code path building manager options.
  • Ensure options object construction does not short-circuit to undefined.
  • Add a local assertion before calling createManager.
  • Add tests for missing-options and valid-options initialization.
Fix Example: pass an explicit options object to createManager
const manager = createManager({
  factory: makeSession,
  max: 10,
  ttl: 300000
});

Session manager requires a factory function.

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:

  • Verify factory exists and is a function.
  • Ensure dependency injection/config wiring does not pass factory results instead of function references.
  • Keep factory async and return Session.
  • Add tests that reject missing factory and accept valid factory functions.
Fix Example: pass a factory callback (not a precomputed value)
const manager = createManager({
  factory: async function makeSession(ctx) {
    return connect(config, ctx.identity.key);
  }
});

ttl must be a positive number.

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:

  • Trace TTL input from environment/config to manager setup.
  • Parse as number and enforce ttl > 0.
  • Choose a TTL aligned with upstream session cost and traffic patterns.
  • Add tests for invalid TTL values and expected expiration behavior.
Fix Example: validate ttl before manager initialization
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 });

License

MIT

Keywords

mcp

FAQs

Package last updated on 04 Mar 2026

Did you know?

Socket

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.

Install

Related posts