/** * Modern (2026-07-28) protocol adapter. * * Binds the NitroStack registry to the official `@modelcontextprotocol/server` * v2 engine: * * - HTTP: `createMcpHandler(() => buildServer())` wrapped with * `toNodeHandler(...)` from `@modelcontextprotocol/node`, mounted on the * Express app. Stateless per-request serving, `server/discover`, per-request * `_meta` envelope, `Mcp-Method`/`Mcp-Name` headers, and cache hints are all * provided by the SDK. * - stdio: `serveStdio(() => buildServer())` from `@modelcontextprotocol/server/stdio`. * * The SDK stamps `_meta['io.modelcontextprotocol/serverInfo']` on every 2026 * response from the `Implementation` handed to `new McpServer(...)`. * * The v2 packages are loaded lazily (dynamic `import`) so a default (legacy) * install never touches them and existing Jest suites never load v2. The SDK's * types use minified re-exports, so this file types the SDK surface loosely at * the boundary while keeping NitroStack's own objects strongly typed. * * @module */ import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; import type { ProtocolAdapter, ProtocolRegistry, ProtocolTransportOptions } from './adapter.js'; import { TaskManager, type TaskData } from '../task.js'; type AnyRecord = Record; export interface ModernProtocolAdapterOptions { legacyMode?: 'stateless' | 'reject'; taskManager?: TaskManager; } export declare class ModernProtocolAdapter implements ProtocolAdapter { private readonly registry; private readonly options; readonly era: "modern"; private handler?; private stdioHandle?; /** Process-local session for the single stdio peer. Not taken from a client header. */ private stdioSessionId?; /** Tool registrations on the pinned stdio server, kept to resync after visibility changes. */ private stdioCatalog?; private stdioResync; private serverSdkPromise?; private readonly taskManager?; /** * HTTP session ids minted by this process. Client-supplied ids are not members. * The first verified subject to use an id owns it. */ private readonly issuedSessions; /** Method the HTTP gate already classified for this request. The SDK consumes the body before the factory runs. */ private readonly gatedMethods; private static readonly ISSUED_SESSION_TTL_MS; private static readonly MAX_ISSUED_SESSIONS; private lastSessionCapWarning; constructor(registry: ProtocolRegistry, options?: ModernProtocolAdapterOptions); private loadServerSdk; private buildServer; /** * Session and auth from the per-request factory context. * `createMcpHandler` supplies `requestInfo` (the HTTP request) and optional `authInfo`. */ private contextFromFactory; private visibilityRequiresSession; /** Methods that proceed without a server-issued session. */ private isSessionFreeMethod; /** * Remember a session id this process minted. * Tests use this to present the same id a successful `initialize` would return. * An id the client invented is not accepted by {@link httpSessionOrThrow}. */ issueSession(id?: string): string; /** True when `id` is unexpired and was minted here. Refreshes its idle timer. */ private touchIssuedSession; /** Drops the least recently active unclaimed session, or the least recently active one if all are claimed. */ private evictIssuedSession; private sweepIssuedSessions; /** * When visibility is installed, an HTTP session id must be one `issueSession` recorded. * A missing or unknown id throws. Stdio does not call this. */ private httpSessionOrThrow; /** * The first verified subject to present a minted id owns it. * A later call with a different subject, or with no subject, is rejected. * An id that has only been used anonymously stays anonymous until a subject claims it. */ private bindIssuedSubject; private verifiedSubject; /** * One context for tools/list, tools/call, and resources/read. * `authInfo` is the verified SDK principal. Metadata may still carry a raw * bearer token; `createExecutionContext` does not use that token as the * isolation subject. */ private executionContextFromRequest; /** Server-level per-operation cache hints for list/discover results. */ private buildServerCacheHints; private registerTools; /** * The stdio SDK pins one server per connection, so its catalog is built once. * Re-run the pipeline for the peer session and add or remove registrations to * match. Each change makes the SDK send `notifications/tools/list_changed`. */ private resyncStdioTools; private registerOneTool; private registerResources; private registerPrompts; private runTool; /** * Translate a NitroStack/handler error into an SDK error carrying the right * JSON-RPC code for the modern era. Prefers the v2 SDK error classes (so the * SDK serializes `error.code` correctly); falls back to a plain `Error` with * a numeric `code` property the SDK also recognizes. */ private toSdkError; private readResource; private buildContext; private mapAuthInfo; private toModernSchema; /** * Build the stateless per-request Node HTTP handler for the modern engine. * Returned so an existing Express host (NitroStack's `StreamableHttpTransport`) * can route its `/mcp` endpoint to it, keeping one Express app and one set of * OAuth/docs routes. */ /** * Build (once) the web-standard v2 handler `{ fetch, close, notify, bus }` * returned by `createMcpHandler`. Idempotent: repeated calls reuse the same * handler so a single notify bus backs `subscriptions/listen`. */ getHttpHandler(): Promise; /** * A 2025-era POST from a client that does not accept `text/event-stream`. * The SDK's stateless fallback rejects it with 406 and only ever answers over * SSE, so rewriting the header alone would hand the client a stream it cannot read. */ private isJsonOnlyLegacyPost; /** The SDK's stateless legacy fallback, with JSON responses instead of SSE. */ private serveLegacyPostAsJson; private sessionRequiredResponse; /** * Session gate for the modern HTTP fetch path. * `initialize`, `ping`, and `server/discover` proceed without a session. * `tools/list`, `tools/call`, and `resources/read` need an id this process minted. * A POST whose method cannot be read fails closed. */ private visibilityHttpGate; /** Stamp a freshly minted session id onto a successful initialize response. */ private attachIssuedSession; /** * Read a finite JSON body, or the first SSE frame. * A legacy initialize answer is a stream that stays open after the handshake. */ private readResponseText; /** First JSON-RPC message from a JSON body or an SSE `data:` frame. */ private extractRpcMessage; /** Raw mcp-session-id from a Web Request or an Express request. Not an isolation key. */ private requestSessionId; /** * Principal attached by the host or the SDK. * `auth` on the request body and `_meta` are not verified and are ignored. */ private trustedAuthInfo; /** SDK authInfo when the host attached it. A bearer header is not verified here. */ private requestAuthInfo; private extractAccessContext; /** * tasks/get, tasks/cancel, and tasks/update carry tool results. When visibility * is on they need the same minted session as tools/call. params.sessionId is * not a credential. */ private taskAccessForRequest; private handleTaskPreDispatch; createNodeHandler(): Promise<(req: ExpressRequest, res: ExpressResponse) => void>; attachHttp(app: Express, options: ProtocolTransportOptions): Promise; /** * SEP-2243/SEP-2575 CORS: expose and allow the new required request headers * (`MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name`, `Mcp-Param-*`, and * `Mcp-Session-Id`). Session visibility and spillover both read that header. */ private applyCorsHeaders; serveStdio(): Promise; notifyToolsListChanged(sessionId?: string): void; notifyResourcesListChanged(): void; notifyPromptsListChanged(): void; notifyResourceUpdated(uri: string): void; notifyTaskStatus(taskData: TaskData): void; close(): Promise; /** The extensions map this adapter would advertise (for diagnostics/notes). */ advertisedExtensions(): Record>; } export {}; //# sourceMappingURL=modern-v2.adapter.d.ts.map