@nevermined-io/payments 1.11.1 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -56,6 +56,26 @@ export declare class SessionManager {
|
|
|
56
56
|
getRequestContext(sessionId: string): RequestContext | undefined;
|
|
57
57
|
/**
|
|
58
58
|
* Get or create a transport for a session.
|
|
59
|
+
*
|
|
60
|
+
* Always builds a FRESH transport, even when a transport is already cached for
|
|
61
|
+
* this session id. The transports are created in stateless mode
|
|
62
|
+
* (`sessionIdGenerator: undefined`), and the SDK's stateless transport is
|
|
63
|
+
* single-request by design — reusing a cached one yields an empty-body HTTP 500
|
|
64
|
+
* on the second request. Spec-compliant clients (Claude, Cursor, the official
|
|
65
|
+
* SDK) echo the `Mcp-Session-Id` we return on `initialize` back on every
|
|
66
|
+
* subsequent request, which used to route them straight into that broken reuse
|
|
67
|
+
* path. Rebuilding per request keeps the echoed-id path working exactly like the
|
|
68
|
+
* no-header path.
|
|
69
|
+
*
|
|
70
|
+
* The session map still records the latest transport per id so DELETE can
|
|
71
|
+
* terminate it and requests for an unknown id can be rejected. This managed
|
|
72
|
+
* server runs in stateless JSON-response mode (`enableJsonResponse: true`) and
|
|
73
|
+
* does NOT support standalone GET/SSE streams — the stateless transport rejects
|
|
74
|
+
* them — so there is no long-lived stream for a later POST to disrupt.
|
|
75
|
+
*
|
|
76
|
+
* ponytail: one shared McpServer that every request `close()`s and reconnects —
|
|
77
|
+
* two in-flight requests still race (tracked in #424). Per-session server+transport
|
|
78
|
+
* pairs are the upgrade if concurrency ever matters.
|
|
59
79
|
*/
|
|
60
80
|
getOrCreateSession(sessionId: string): Promise<any>;
|
|
61
81
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/mcp/http/session-manager.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAmBD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C,2CAA2C;IAC3C,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAChD,sBAAsB;IACtB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,oBAAyB;IAI7C;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;IAI/B;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAI9C;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAInE;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIhE
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/mcp/http/session-manager.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAmBD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C,2CAA2C;IAC3C,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAChD,sBAAsB;IACtB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,oBAAyB;IAI7C;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;IAI/B;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAI9C;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAInE;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIhE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAqDzD;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAsB1C;;OAEG;IACH,iBAAiB,IAAI,MAAM,EAAE;IAI7B;;OAEG;IACH,eAAe,IAAI,MAAM;IAIzB;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAQ1B;;OAEG;IACH,OAAO,CAAC,GAAG;CAGZ;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,oBAAyB,GAAG,cAAc,CAEtF"}
|
|
@@ -71,14 +71,28 @@ export class SessionManager {
|
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* Get or create a transport for a session.
|
|
74
|
+
*
|
|
75
|
+
* Always builds a FRESH transport, even when a transport is already cached for
|
|
76
|
+
* this session id. The transports are created in stateless mode
|
|
77
|
+
* (`sessionIdGenerator: undefined`), and the SDK's stateless transport is
|
|
78
|
+
* single-request by design — reusing a cached one yields an empty-body HTTP 500
|
|
79
|
+
* on the second request. Spec-compliant clients (Claude, Cursor, the official
|
|
80
|
+
* SDK) echo the `Mcp-Session-Id` we return on `initialize` back on every
|
|
81
|
+
* subsequent request, which used to route them straight into that broken reuse
|
|
82
|
+
* path. Rebuilding per request keeps the echoed-id path working exactly like the
|
|
83
|
+
* no-header path.
|
|
84
|
+
*
|
|
85
|
+
* The session map still records the latest transport per id so DELETE can
|
|
86
|
+
* terminate it and requests for an unknown id can be rejected. This managed
|
|
87
|
+
* server runs in stateless JSON-response mode (`enableJsonResponse: true`) and
|
|
88
|
+
* does NOT support standalone GET/SSE streams — the stateless transport rejects
|
|
89
|
+
* them — so there is no long-lived stream for a later POST to disrupt.
|
|
90
|
+
*
|
|
91
|
+
* ponytail: one shared McpServer that every request `close()`s and reconnects —
|
|
92
|
+
* two in-flight requests still race (tracked in #424). Per-session server+transport
|
|
93
|
+
* pairs are the upgrade if concurrency ever matters.
|
|
74
94
|
*/
|
|
75
95
|
async getOrCreateSession(sessionId) {
|
|
76
|
-
if (this.sessions.has(sessionId)) {
|
|
77
|
-
const existingSession = this.sessions.get(sessionId);
|
|
78
|
-
if (existingSession) {
|
|
79
|
-
return existingSession;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
96
|
if (!this.mcpServer) {
|
|
83
97
|
throw new Error('MCP server not set. Call setMcpServer() first.');
|
|
84
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../../src/mcp/http/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session manager for MCP transports.\n * Handles creation, retrieval, and cleanup of StreamableHTTPServerTransport instances.\n * Also stores request context (headers) per session for authentication.\n */\nimport { randomUUID } from 'crypto'\n\n// We'll dynamically import the SDK to avoid hard dependency issues\nlet StreamableHTTPServerTransport: any = null\n\n/**\n * Request context stored per session.\n * Contains HTTP headers and other request info needed for authentication.\n */\nexport interface RequestContext {\n headers: Record<string, string | string[] | undefined>\n method?: string\n url?: string\n ip?: string\n}\n\n/**\n * Lazily load the MCP SDK transport.\n */\nasync function getTransportClass(): Promise<any> {\n if (!StreamableHTTPServerTransport) {\n try {\n const module = await import('@modelcontextprotocol/sdk/server/streamableHttp.js')\n StreamableHTTPServerTransport = module.StreamableHTTPServerTransport\n } catch (error) {\n throw new Error(\n 'Failed to load @modelcontextprotocol/sdk. Make sure it is installed: npm install @modelcontextprotocol/sdk',\n )\n }\n }\n return StreamableHTTPServerTransport\n}\n\n/**\n * Configuration for session manager.\n */\nexport interface SessionManagerConfig {\n /** Callback when a session is created */\n onSessionCreated?: (sessionId: string) => void\n /** Callback when a session is destroyed */\n onSessionDestroyed?: (sessionId: string) => void\n /** Logger function */\n log?: (message: string) => void\n}\n\n/**\n * Manages MCP transport sessions.\n */\nexport class SessionManager {\n private sessions: Map<string, any> = new Map()\n private requestContexts: Map<string, RequestContext> = new Map()\n private mcpServer: any = null\n private config: SessionManagerConfig\n\n constructor(config: SessionManagerConfig = {}) {\n this.config = config\n }\n\n /**\n * Set the MCP server that transports will connect to.\n */\n setMcpServer(server: any): void {\n this.mcpServer = server\n }\n\n /**\n * Generate a new session ID.\n */\n generateSessionId(): string {\n return randomUUID()\n }\n\n /**\n * Check if a session exists.\n */\n hasSession(sessionId: string): boolean {\n return this.sessions.has(sessionId)\n }\n\n /**\n * Get an existing session's transport.\n */\n getSession(sessionId: string): any | undefined {\n return this.sessions.get(sessionId)\n }\n\n /**\n * Store request context (headers, etc.) for a session.\n * This is called when an HTTP request arrives, before dispatching to MCP.\n */\n setRequestContext(sessionId: string, context: RequestContext): void {\n this.requestContexts.set(sessionId, context)\n }\n\n /**\n * Get the stored request context for a session.\n * Returns undefined if no context is stored.\n */\n getRequestContext(sessionId: string): RequestContext | undefined {\n return this.requestContexts.get(sessionId)\n }\n\n /**\n * Get or create a transport for a session.\n */\n async getOrCreateSession(sessionId: string): Promise<any> {\n if (this.sessions.has(sessionId)) {\n const existingSession = this.sessions.get(sessionId)\n if (existingSession) {\n return existingSession\n }\n }\n\n if (!this.mcpServer) {\n throw new Error('MCP server not set. Call setMcpServer() first.')\n }\n\n // In stateless mode (sessionIdGenerator: undefined), each request gets a new transport\n // but McpServer can only have one transport at a time. Close the existing connection\n // before connecting the new transport so Protocol._transport is reset to undefined.\n // The StreamableHTTPServerTransport with enableJsonResponse does not auto-close after\n // sending a JSON response, so we must close it explicitly before each new connection.\n try {\n await this.mcpServer.close()\n } catch (_e) {\n // Ignore close errors — server may not have been connected yet\n }\n\n const TransportClass = await getTransportClass()\n\n // Use stateless mode (sessionIdGenerator: undefined) to avoid requiring\n // Mcp-Session-Id header on every request. This allows clients to make\n // direct JSON-RPC calls without the full MCP initialization handshake.\n const transport = new TransportClass({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n })\n\n // Set onclose BEFORE connect() so the SDK chains it with its own handler.\n // When the transport closes, both our cleanup and the SDK's cleanup run.\n transport.onclose = () => {\n this.sessions.delete(sessionId)\n this.requestContexts.delete(sessionId)\n this.config.onSessionDestroyed?.(sessionId)\n this.log?.(`Transport closed for session ${sessionId}`)\n }\n\n await this.mcpServer.connect(transport)\n\n // Force _initialized to true to allow requests without requiring initialize first.\n // This maintains backward compatibility with clients that don't send initialize.\n // The SDK sets _initialized = true only after processing an initialize request,\n // but we want to accept tool/resource/prompt calls directly.\n if (transport._initialized === false) {\n transport._initialized = true\n }\n\n this.sessions.set(sessionId, transport)\n\n this.log?.(`Created new transport for session ${sessionId}`)\n this.config.onSessionCreated?.(sessionId)\n\n return transport\n }\n\n /**\n * Destroy a session.\n */\n destroySession(sessionId: string): boolean {\n if (this.sessions.has(sessionId)) {\n const transport = this.sessions.get(sessionId)\n this.sessions.delete(sessionId)\n this.requestContexts.delete(sessionId) // Clean up request context\n this.log?.(`Destroyed session ${sessionId}`)\n this.config.onSessionDestroyed?.(sessionId)\n\n // Try to close the transport if it has a close method\n if (transport && typeof transport.close === 'function') {\n try {\n transport.close()\n } catch {\n // Ignore close errors\n }\n }\n\n return true\n }\n return false\n }\n\n /**\n * Get all active session IDs.\n */\n getActiveSessions(): string[] {\n return Array.from(this.sessions.keys())\n }\n\n /**\n * Get the number of active sessions.\n */\n getSessionCount(): number {\n return this.sessions.size\n }\n\n /**\n * Destroy all sessions.\n */\n destroyAllSessions(): void {\n const sessionIds = this.getActiveSessions()\n for (const sessionId of sessionIds) {\n this.destroySession(sessionId)\n }\n this.log?.(`Destroyed all ${sessionIds.length} sessions`)\n }\n\n /**\n * Internal logging helper (no-op if log not provided).\n */\n private log(message: string): void {\n this.config.log?.(message)\n }\n}\n\n/**\n * Create a new session manager.\n */\nexport function createSessionManager(config: SessionManagerConfig = {}): SessionManager {\n return new SessionManager(config)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../../src/mcp/http/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session manager for MCP transports.\n * Handles creation, retrieval, and cleanup of StreamableHTTPServerTransport instances.\n * Also stores request context (headers) per session for authentication.\n */\nimport { randomUUID } from 'crypto'\n\n// We'll dynamically import the SDK to avoid hard dependency issues\nlet StreamableHTTPServerTransport: any = null\n\n/**\n * Request context stored per session.\n * Contains HTTP headers and other request info needed for authentication.\n */\nexport interface RequestContext {\n headers: Record<string, string | string[] | undefined>\n method?: string\n url?: string\n ip?: string\n}\n\n/**\n * Lazily load the MCP SDK transport.\n */\nasync function getTransportClass(): Promise<any> {\n if (!StreamableHTTPServerTransport) {\n try {\n const module = await import('@modelcontextprotocol/sdk/server/streamableHttp.js')\n StreamableHTTPServerTransport = module.StreamableHTTPServerTransport\n } catch (error) {\n throw new Error(\n 'Failed to load @modelcontextprotocol/sdk. Make sure it is installed: npm install @modelcontextprotocol/sdk',\n )\n }\n }\n return StreamableHTTPServerTransport\n}\n\n/**\n * Configuration for session manager.\n */\nexport interface SessionManagerConfig {\n /** Callback when a session is created */\n onSessionCreated?: (sessionId: string) => void\n /** Callback when a session is destroyed */\n onSessionDestroyed?: (sessionId: string) => void\n /** Logger function */\n log?: (message: string) => void\n}\n\n/**\n * Manages MCP transport sessions.\n */\nexport class SessionManager {\n private sessions: Map<string, any> = new Map()\n private requestContexts: Map<string, RequestContext> = new Map()\n private mcpServer: any = null\n private config: SessionManagerConfig\n\n constructor(config: SessionManagerConfig = {}) {\n this.config = config\n }\n\n /**\n * Set the MCP server that transports will connect to.\n */\n setMcpServer(server: any): void {\n this.mcpServer = server\n }\n\n /**\n * Generate a new session ID.\n */\n generateSessionId(): string {\n return randomUUID()\n }\n\n /**\n * Check if a session exists.\n */\n hasSession(sessionId: string): boolean {\n return this.sessions.has(sessionId)\n }\n\n /**\n * Get an existing session's transport.\n */\n getSession(sessionId: string): any | undefined {\n return this.sessions.get(sessionId)\n }\n\n /**\n * Store request context (headers, etc.) for a session.\n * This is called when an HTTP request arrives, before dispatching to MCP.\n */\n setRequestContext(sessionId: string, context: RequestContext): void {\n this.requestContexts.set(sessionId, context)\n }\n\n /**\n * Get the stored request context for a session.\n * Returns undefined if no context is stored.\n */\n getRequestContext(sessionId: string): RequestContext | undefined {\n return this.requestContexts.get(sessionId)\n }\n\n /**\n * Get or create a transport for a session.\n *\n * Always builds a FRESH transport, even when a transport is already cached for\n * this session id. The transports are created in stateless mode\n * (`sessionIdGenerator: undefined`), and the SDK's stateless transport is\n * single-request by design — reusing a cached one yields an empty-body HTTP 500\n * on the second request. Spec-compliant clients (Claude, Cursor, the official\n * SDK) echo the `Mcp-Session-Id` we return on `initialize` back on every\n * subsequent request, which used to route them straight into that broken reuse\n * path. Rebuilding per request keeps the echoed-id path working exactly like the\n * no-header path.\n *\n * The session map still records the latest transport per id so DELETE can\n * terminate it and requests for an unknown id can be rejected. This managed\n * server runs in stateless JSON-response mode (`enableJsonResponse: true`) and\n * does NOT support standalone GET/SSE streams — the stateless transport rejects\n * them — so there is no long-lived stream for a later POST to disrupt.\n *\n * ponytail: one shared McpServer that every request `close()`s and reconnects —\n * two in-flight requests still race (tracked in #424). Per-session server+transport\n * pairs are the upgrade if concurrency ever matters.\n */\n async getOrCreateSession(sessionId: string): Promise<any> {\n if (!this.mcpServer) {\n throw new Error('MCP server not set. Call setMcpServer() first.')\n }\n\n // In stateless mode (sessionIdGenerator: undefined), each request gets a new transport\n // but McpServer can only have one transport at a time. Close the existing connection\n // before connecting the new transport so Protocol._transport is reset to undefined.\n // The StreamableHTTPServerTransport with enableJsonResponse does not auto-close after\n // sending a JSON response, so we must close it explicitly before each new connection.\n try {\n await this.mcpServer.close()\n } catch (_e) {\n // Ignore close errors — server may not have been connected yet\n }\n\n const TransportClass = await getTransportClass()\n\n // Use stateless mode (sessionIdGenerator: undefined) to avoid requiring\n // Mcp-Session-Id header on every request. This allows clients to make\n // direct JSON-RPC calls without the full MCP initialization handshake.\n const transport = new TransportClass({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n })\n\n // Set onclose BEFORE connect() so the SDK chains it with its own handler.\n // When the transport closes, both our cleanup and the SDK's cleanup run.\n transport.onclose = () => {\n this.sessions.delete(sessionId)\n this.requestContexts.delete(sessionId)\n this.config.onSessionDestroyed?.(sessionId)\n this.log?.(`Transport closed for session ${sessionId}`)\n }\n\n await this.mcpServer.connect(transport)\n\n // Force _initialized to true to allow requests without requiring initialize first.\n // This maintains backward compatibility with clients that don't send initialize.\n // The SDK sets _initialized = true only after processing an initialize request,\n // but we want to accept tool/resource/prompt calls directly.\n if (transport._initialized === false) {\n transport._initialized = true\n }\n\n this.sessions.set(sessionId, transport)\n\n this.log?.(`Created new transport for session ${sessionId}`)\n this.config.onSessionCreated?.(sessionId)\n\n return transport\n }\n\n /**\n * Destroy a session.\n */\n destroySession(sessionId: string): boolean {\n if (this.sessions.has(sessionId)) {\n const transport = this.sessions.get(sessionId)\n this.sessions.delete(sessionId)\n this.requestContexts.delete(sessionId) // Clean up request context\n this.log?.(`Destroyed session ${sessionId}`)\n this.config.onSessionDestroyed?.(sessionId)\n\n // Try to close the transport if it has a close method\n if (transport && typeof transport.close === 'function') {\n try {\n transport.close()\n } catch {\n // Ignore close errors\n }\n }\n\n return true\n }\n return false\n }\n\n /**\n * Get all active session IDs.\n */\n getActiveSessions(): string[] {\n return Array.from(this.sessions.keys())\n }\n\n /**\n * Get the number of active sessions.\n */\n getSessionCount(): number {\n return this.sessions.size\n }\n\n /**\n * Destroy all sessions.\n */\n destroyAllSessions(): void {\n const sessionIds = this.getActiveSessions()\n for (const sessionId of sessionIds) {\n this.destroySession(sessionId)\n }\n this.log?.(`Destroyed all ${sessionIds.length} sessions`)\n }\n\n /**\n * Internal logging helper (no-op if log not provided).\n */\n private log(message: string): void {\n this.config.log?.(message)\n }\n}\n\n/**\n * Create a new session manager.\n */\nexport function createSessionManager(config: SessionManagerConfig = {}): SessionManager {\n return new SessionManager(config)\n}\n"]}
|