Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@mcp-layer/gateway

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mcp-layer/gateway - npm Package Compare versions

Comparing version
0.2.1
to
0.2.2
+2
-2
package.json
{
"name": "@mcp-layer/gateway",
"version": "0.2.1",
"version": "0.2.2",
"description": "Shared runtime primitives for MCP HTTP/GraphQL adapters with validation, resilience, telemetry, and catalog mapping.",

@@ -45,3 +45,3 @@ "repository": {

"@mcp-layer/error": "0.2.1",
"@mcp-layer/schema": "1.0.3"
"@mcp-layer/schema": "1.0.4"
},

@@ -48,0 +48,0 @@ "peerDependencies": {

+95
-14

@@ -38,3 +38,3 @@ # @mcp-layer/gateway

Expected behavior: one runtime context is created per bootstrap session, with a stable prefix/version and reusable resolver/validator/executor primitives.
Expected behavior: one runtime context is created per bootstrap session or precomputed catalog, with a stable prefix/version and reusable resolver/validator/executor primitives.

@@ -85,3 +85,4 @@ ```js

options: {
session: Session | Session[];
session?: Session | Session[];
catalog?: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> };
manager?: { get(request): Promise<Session>; close?(): Promise<void> };

@@ -122,3 +123,3 @@ prefix?: string | ((version, info, sessionName) => string);

contexts: Array<{
session;
session | undefined;
catalog;

@@ -142,3 +143,5 @@ info;

- `manager` requires a bootstrap `session` (catalog extraction source).
- `manager` requires either a bootstrap `session` or a precomputed `catalog`.
- when a bootstrap `session` exists, runtime metadata is always extracted from that live session.
- `catalog` bootstrap is only used when manager mode has no bootstrap session yet.
- manager mode does not support `session` arrays.

@@ -285,3 +288,3 @@ - `close()` shuts down breaker instances and calls `manager.close()` when available.

Step-by-step resolution:
1. In manager mode, provide exactly one bootstrap session.
1. In manager mode, provide exactly one bootstrap session or one precomputed catalog.
2. If you need multiple static sessions, remove manager mode.

@@ -291,3 +294,3 @@ 3. Register separate adapter instances for each static session surface.

This example demonstrates valid manager-mode bootstrapping. This matters because manager mode resolves sessions per request and needs one catalog source at startup. Expected behavior: runtime initializes and delegates per-request resolution through manager.
This example demonstrates valid manager-mode bootstrapping. This matters because manager mode resolves sessions per request and still needs one catalog source at startup. Expected behavior: runtime initializes and delegates per-request resolution through manager.

@@ -306,16 +309,16 @@ <details>

<a id="error-c773d9"></a>
### `session is required when manager is provided (used for catalog bootstrap).`
<a id="error-b010d3"></a>
### `session or catalog is required when manager is provided (used for catalog bootstrap).`
Thrown from: `validateRuntimeOptions`
This happens when `manager` is configured without a bootstrap `session`.
This happens when `manager` is configured without a bootstrap `session` or a precomputed `catalog`.
Step-by-step resolution:
1. Provide a bootstrap session alongside manager.
2. Ensure bootstrap session connects before runtime creation.
3. Keep bootstrap session aligned with manager-provided session capabilities.
1. Provide a bootstrap `session` or a precomputed `catalog` alongside manager.
2. Ensure bootstrap session connects before runtime creation, or ensure the catalog matches the manager-provided session surface.
3. Keep the catalog aligned with manager-provided session capabilities.
4. Add a startup test that asserts this requirement.
This example shows manager mode with explicit bootstrap catalog source. This matters because the runtime must extract catalog metadata at initialization time. Expected behavior: runtime can build validators and route metadata before serving requests.
This example shows manager mode with explicit bootstrap catalog source. This matters because the runtime must know route and validator metadata before serving requests. Expected behavior: runtime can build validators and route metadata before request execution begins.

@@ -334,2 +337,46 @@ <details>

<details>
<summary>Fix Example: include catalog when manager is enabled</summary>
```js
await createRuntime({
catalog,
manager
});
```
</details>
<a id="error-17712f"></a>
### `catalog must be an object.`
Thrown from: `validateRuntimeOptions`
This happens when `catalog` is provided as a primitive, array, or other non-object value.
Step-by-step resolution:
1. Pass the extracted or composed catalog as a plain object.
2. Do not pass serialized JSON strings or arrays in place of the catalog root.
3. Ensure the object shape includes `server` and `items` only where needed.
4. Add tests that reject malformed catalog bootstrap values.
This example shows valid catalog bootstrap input. This matters because manager mode can depend on catalog metadata before any request-scoped session exists. Expected behavior: runtime accepts the catalog and registers validators and route metadata from it.
<details>
<summary>Fix Example: pass catalog as an object</summary>
```js
await createRuntime({
catalog: {
server: {
info: { name: 'example-server', version: '1.0.0' }
},
items: []
},
manager
});
```
</details>
<a id="error-6e22f2"></a>

@@ -344,3 +391,3 @@ ### `session or manager option is required.`

1. Pass a connected `session` for static mode.
2. Or pass `manager` plus a bootstrap `session` for dynamic mode.
2. Or pass `manager` plus a bootstrap `session` or `catalog` for dynamic mode.
3. Add adapter-level assertions before calling `createRuntime`.

@@ -475,4 +522,38 @@ 4. Add tests that verify startup failure without session sources.

<a id="error-a05713"></a>
### `No session available for request resolution.`
Thrown from: `resolveSession`
This happens when request-time session resolution returns `undefined` and no bootstrap session was available to fall back to.
Step-by-step resolution:
1. Verify manager mode always returns a `Session` from `get(request)`.
2. Ensure static mode passes a connected bootstrap `session`.
3. Confirm request routing reaches the adapter instance backed by the expected manager or session.
4. Add a request-path test that exercises the failing resolution path directly.
This example demonstrates a manager that always resolves a concrete session. This matters because runtime execution cannot validate or execute MCP methods without a request-scoped target session. Expected behavior: each request resolves a session and proceeds to breaker-backed execution.
<details>
<summary>Fix Example: always return a session from manager.get</summary>
```js
const manager = {
async get(request) {
const session = await lookupSession(request);
if (!session) throw new Error('session lookup failed');
return session;
}
};
await createRuntime({
catalog,
manager
});
```
</details>
## License
MIT

@@ -62,3 +62,3 @@ import { defaults } from './defaults.js';

* @param {{ name?: string, serviceName?: string }} [meta] - Validation metadata.
* @returns {{ session: unknown, manager?: { get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session>, close?: () => Promise<void> }, prefix?: string | ((version: string, info: Record<string, unknown> | undefined, name: string) => string), validation: { trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }, resilience: { enabled: boolean, timeout: number, errorThresholdPercentage: number, resetTimeout: number, volumeThreshold: number }, telemetry: { enabled: boolean, serviceName: string, metricPrefix: string, api?: import('@opentelemetry/api') }, errors: { exposeDetails: boolean }, normalizeError?: (error: Error & { code?: string | number }, instance: string, requestId?: string, options?: { exposeDetails?: boolean }) => unknown }}
* @returns {{ session?: unknown, catalog?: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, manager?: { get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session>, close?: () => Promise<void> }, prefix?: string | ((version: string, info: Record<string, unknown> | undefined, name: string) => string), validation: { trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }, resilience: { enabled: boolean, timeout: number, errorThresholdPercentage: number, resetTimeout: number, volumeThreshold: number }, telemetry: { enabled: boolean, serviceName: string, metricPrefix: string, api?: import('@opentelemetry/api') }, errors: { exposeDetails: boolean }, normalizeError?: (error: Error & { code?: string | number }, instance: string, requestId?: string, options?: { exposeDetails?: boolean }) => unknown }}
*/

@@ -73,2 +73,3 @@ export function validateRuntimeOptions(opts, meta = {}) {

const session = input.session;
const catalog = input.catalog;
const manager = input.manager;

@@ -84,2 +85,10 @@

if (catalog !== undefined && !isrecord(catalog)) {
throw new LayerError({
name: pack,
method: 'validateRuntimeOptions',
message: 'catalog must be an object.',
});
}
if (manager !== undefined) {

@@ -93,7 +102,7 @@ if (!isrecord(manager) || typeof manager.get !== 'function') {

}
if (!session) {
if (!session && !catalog) {
throw new LayerError({
name: pack,
method: 'validateRuntimeOptions',
message: 'session is required when manager is provided (used for catalog bootstrap).',
message: 'session or catalog is required when manager is provided (used for catalog bootstrap).',
});

@@ -161,2 +170,3 @@ }

session,
catalog: /** @type {{ server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> } | undefined} */ (catalog),
manager: /** @type {{ get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session>, close?: () => Promise<void> } | undefined} */ (manager),

@@ -163,0 +173,0 @@ prefix: input.prefix,

import { extract } from '@mcp-layer/schema';
import { LayerError } from '@mcp-layer/error';
import { createValidator } from './validation/validator.js';

@@ -18,2 +19,24 @@ import { createCircuitBreaker, executeWithBreaker } from './resilience/breaker.js';

/**
* Resolve the logical context name used for prefix callbacks.
* Prefer the explicit session name, then the catalog server name, and finally a
* stable fallback so catalog-driven manager mode can register routes without a
* bootstrap session.
*
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
* @param {Record<string, unknown> | undefined} info - Catalog server metadata.
* @returns {string}
*/
function contextName(session, info) {
if (session && typeof session.name === 'string' && session.name.length > 0) {
return session.name;
}
if (info && typeof info.name === 'string' && info.name.length > 0) {
return String(info.name);
}
return 'session';
}
/**
* Register tool and prompt validators from a catalog.

@@ -73,3 +96,3 @@ * @param {import('./validation/validator.js').SchemaValidator} validator - Schema validator.

* Resolve request-scoped session and breaker.
* @param {import('@mcp-layer/session').Session} bootstrap - Bootstrap session.
* @param {import('@mcp-layer/session').Session | undefined} bootstrap - Optional bootstrap session.
* @param {{ manager?: { get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session> }, resilience: { enabled: boolean, timeout: number, errorThresholdPercentage: number, resetTimeout: number, volumeThreshold: number } }} config - Runtime config.

@@ -83,2 +106,9 @@ * @param {Map<string, import('opossum')>} breakers - Breaker storage.

const target = config.manager ? await config.manager.get(request) : bootstrap;
if (!target) {
throw new LayerError({
name: 'gateway',
method: 'resolveSession',
message: 'No session available for request resolution.',
});
}
const breaker = ensureBreaker(breakers, target, config.resilience, telemetry);

@@ -92,7 +122,9 @@ return { session: target, breaker };

* @param {{ name?: string, serviceName?: string }} [meta] - Runtime metadata.
* @returns {Promise<{ config: ReturnType<typeof validateRuntimeOptions>, contexts: Array<{ session: import('@mcp-layer/session').Session, catalog: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, info: Record<string, unknown> | undefined, version: string, prefix: string, validator: import('./validation/validator.js').SchemaValidator, telemetry: ReturnType<typeof createTelemetry> | null, resolve: (request: import('fastify').FastifyRequest) => Promise<{ session: import('@mcp-layer/session').Session, breaker: import('opossum') | null }>, execute: (request: import('fastify').FastifyRequest, method: string, params: Record<string, unknown>) => Promise<Record<string, unknown>>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown }>, breakers: Map<string, import('opossum')>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown, close: () => Promise<void> }>}
* @returns {Promise<{ config: ReturnType<typeof validateRuntimeOptions>, contexts: Array<{ session: import('@mcp-layer/session').Session | undefined, catalog: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, info: Record<string, unknown> | undefined, version: string, prefix: string, validator: import('./validation/validator.js').SchemaValidator, telemetry: ReturnType<typeof createTelemetry> | null, resolve: (request: import('fastify').FastifyRequest) => Promise<{ session: import('@mcp-layer/session').Session, breaker: import('opossum') | null }>, execute: (request: import('fastify').FastifyRequest, method: string, params: Record<string, unknown>) => Promise<Record<string, unknown>>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown }>, breakers: Map<string, import('opossum')>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown, close: () => Promise<void> }>}
*/
export async function createRuntime(opts, meta = {}) {
const config = validateRuntimeOptions(opts, meta);
const sessions = config.manager ? [config.session] : list(config.session);
const sessions = config.manager
? (config.session ? [config.session] : [])
: list(config.session);
const breakers = new Map();

@@ -121,7 +153,15 @@ const contexts = [];

for (const session of sessions) {
const catalog = await extract(session);
/**
* Build and store one runtime context from a bootstrap session or a
* precomputed catalog. Manager mode can omit the session entirely when a
* catalog was provided explicitly.
*
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
* @param {{ server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }} catalog - Extracted or precomputed catalog.
* @returns {void}
*/
function pushContext(session, catalog) {
const info = catalog.server?.info;
const version = deriveApiVersion(info);
const prefix = resolvePrefix(config.prefix, version, info, session.name);
const prefix = resolvePrefix(config.prefix, version, info, contextName(session, info));
const validator = createValidator(config.validation, session);

@@ -178,2 +218,14 @@ const telemetry = createTelemetry(config.telemetry);

if (config.manager && !config.session) {
pushContext(undefined, config.catalog);
} else {
for (const session of sessions) {
// A live bootstrap session is the source of truth for route metadata.
// Explicit catalogs are only for manager mode when no bootstrap session
// exists yet.
const catalog = await extract(session);
pushContext(session, catalog);
}
}
/**

@@ -180,0 +232,0 @@ * Close runtime resources.

/**
* Determine whether schemas from a session should be trusted.
* @param {import('@mcp-layer/session').Session} session - MCP session to evaluate.
* Missing bootstrap sessions are treated conservatively because catalog-only
* manager mode may be registering schemas sourced from a remote runtime.
*
* @param {import('@mcp-layer/session').Session | undefined} session - Optional MCP session to evaluate.
* @param {'auto' | true | false} policy - Trust policy.

@@ -10,2 +13,3 @@ * @returns {boolean}

if (policy === false) return false;
if (!session) return false;

@@ -12,0 +16,0 @@ const type = session?.transport?.constructor?.name;

@@ -12,3 +12,3 @@ import Ajv from 'ajv';

* @param {{ trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }} config - Validation configuration.
* @param {import('@mcp-layer/session').Session} session - MCP session.
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
*/

@@ -143,3 +143,3 @@ constructor(config, session) {

* @param {{ trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }} config - Validation configuration.
* @param {import('@mcp-layer/session').Session} session - MCP session.
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
* @returns {SchemaValidator}

@@ -146,0 +146,0 @@ */

@@ -5,3 +5,3 @@ /**

* @param {{ name?: string, serviceName?: string }} [meta] - Validation metadata.
* @returns {{ session: unknown, manager?: { get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session>, close?: () => Promise<void> }, prefix?: string | ((version: string, info: Record<string, unknown> | undefined, name: string) => string), validation: { trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }, resilience: { enabled: boolean, timeout: number, errorThresholdPercentage: number, resetTimeout: number, volumeThreshold: number }, telemetry: { enabled: boolean, serviceName: string, metricPrefix: string, api?: import('@opentelemetry/api') }, errors: { exposeDetails: boolean }, normalizeError?: (error: Error & { code?: string | number }, instance: string, requestId?: string, options?: { exposeDetails?: boolean }) => unknown }}
* @returns {{ session?: unknown, catalog?: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, manager?: { get: (request: import('fastify').FastifyRequest) => Promise<import('@mcp-layer/session').Session>, close?: () => Promise<void> }, prefix?: string | ((version: string, info: Record<string, unknown> | undefined, name: string) => string), validation: { trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }, resilience: { enabled: boolean, timeout: number, errorThresholdPercentage: number, resetTimeout: number, volumeThreshold: number }, telemetry: { enabled: boolean, serviceName: string, metricPrefix: string, api?: import('@opentelemetry/api') }, errors: { exposeDetails: boolean }, normalizeError?: (error: Error & { code?: string | number }, instance: string, requestId?: string, options?: { exposeDetails?: boolean }) => unknown }}
*/

@@ -12,3 +12,9 @@ export function validateRuntimeOptions(opts: Record<string, unknown>, meta?: {

}): {
session: unknown;
session?: unknown;
catalog?: {
server?: {
info?: Record<string, unknown>;
};
items?: Array<Record<string, unknown>>;
};
manager?: {

@@ -15,0 +21,0 @@ get: (request: import("fastify").FastifyRequest) => Promise<any>;

@@ -5,3 +5,3 @@ /**

* @param {{ name?: string, serviceName?: string }} [meta] - Runtime metadata.
* @returns {Promise<{ config: ReturnType<typeof validateRuntimeOptions>, contexts: Array<{ session: import('@mcp-layer/session').Session, catalog: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, info: Record<string, unknown> | undefined, version: string, prefix: string, validator: import('./validation/validator.js').SchemaValidator, telemetry: ReturnType<typeof createTelemetry> | null, resolve: (request: import('fastify').FastifyRequest) => Promise<{ session: import('@mcp-layer/session').Session, breaker: import('opossum') | null }>, execute: (request: import('fastify').FastifyRequest, method: string, params: Record<string, unknown>) => Promise<Record<string, unknown>>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown }>, breakers: Map<string, import('opossum')>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown, close: () => Promise<void> }>}
* @returns {Promise<{ config: ReturnType<typeof validateRuntimeOptions>, contexts: Array<{ session: import('@mcp-layer/session').Session | undefined, catalog: { server?: { info?: Record<string, unknown> }, items?: Array<Record<string, unknown>> }, info: Record<string, unknown> | undefined, version: string, prefix: string, validator: import('./validation/validator.js').SchemaValidator, telemetry: ReturnType<typeof createTelemetry> | null, resolve: (request: import('fastify').FastifyRequest) => Promise<{ session: import('@mcp-layer/session').Session, breaker: import('opossum') | null }>, execute: (request: import('fastify').FastifyRequest, method: string, params: Record<string, unknown>) => Promise<Record<string, unknown>>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown }>, breakers: Map<string, import('opossum')>, normalize: (error: Error & { code?: string | number }, instance: string, requestId?: string) => unknown, close: () => Promise<void> }>}
*/

@@ -14,3 +14,3 @@ export function createRuntime(opts: Record<string, unknown>, meta?: {

contexts: Array<{
session: any;
session: any | undefined;
catalog: {

@@ -17,0 +17,0 @@ server?: {

/**
* Determine whether schemas from a session should be trusted.
* @param {import('@mcp-layer/session').Session} session - MCP session to evaluate.
* Missing bootstrap sessions are treated conservatively because catalog-only
* manager mode may be registering schemas sourced from a remote runtime.
*
* @param {import('@mcp-layer/session').Session | undefined} session - Optional MCP session to evaluate.
* @param {'auto' | true | false} policy - Trust policy.
* @returns {boolean}
*/
export function shouldTrustSchemas(session: any, policy: "auto" | true | false): boolean;
export function shouldTrustSchemas(session: any | undefined, policy: "auto" | true | false): boolean;
/**
* Create a schema validator instance.
* @param {{ trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }} config - Validation configuration.
* @param {import('@mcp-layer/session').Session} session - MCP session.
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
* @returns {SchemaValidator}

@@ -14,3 +14,3 @@ */

maxTemplateParamLength: number;
}, session: any): SchemaValidator;
}, session: any | undefined): SchemaValidator;
/**

@@ -22,3 +22,3 @@ * Schema validator for MCP tool and prompt inputs.

* @param {{ trustSchemas: 'auto' | true | false, maxSchemaDepth: number, maxSchemaSize: number, maxPatternLength: number, maxToolNameLength: number, maxTemplateParamLength: number }} config - Validation configuration.
* @param {import('@mcp-layer/session').Session} session - MCP session.
* @param {import('@mcp-layer/session').Session | undefined} session - Optional bootstrap session.
*/

@@ -32,3 +32,3 @@ constructor(config: {

maxTemplateParamLength: number;
}, session: any);
}, session: any | undefined);
/**

@@ -35,0 +35,0 @@ * Register a tool schema for validation.