Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion libs/sdk/src/auth/session/__tests__/session-id.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ describe('session-id.utils', () => {
});

expect(updated).not.toBeNull();
expect(typeof updated).toBe('string');
});

it('should merge partial updates correctly', () => {
Expand Down Expand Up @@ -278,7 +279,7 @@ describe('session-id.utils', () => {
expect(payload.skillsOnlyMode).toBe(true);
});

it('should return false for non-existent session', () => {
it('should return null for non-existent session', () => {
mockSafeDecrypt.mockReturnValue(null);

const result = updateSessionPayload('non-existent-session-id', {
Expand Down
67 changes: 39 additions & 28 deletions libs/sdk/src/transport/mcp-handlers/initialize-request.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,29 @@ import { UnsupportedClientVersionError } from '../../errors';
import type { ClientCapabilities } from '../../notification';
import { detectPlatformFromCapabilities, detectAIPlatform, supportsElicitation } from '../../notification';
import { updateSessionPayload } from '../../auth/session/utils/session-id.utils';
import type { SessionIdPayload } from '@frontmcp/auth';
import type { SdkAuthInfo } from '../../server/server.types';

/**
* Persist initialization data to the session cache and transport adapter.
*
* The returned new session ID from updateSessionPayload is intentionally not
* propagated to the client. The transport session ID is fixed at creation —
* changing it mid-session would break MCP protocol (the client references the
* original ID). The re-encrypted payload is cached under both old and new IDs
* for future lookups on any node.
*/
function persistInitPayload(
sessionId: string,
initPayload: Partial<SessionIdPayload>,
ctx: { authInfo?: unknown },
): void {
updateSessionPayload(sessionId, initPayload);

const transport = (ctx.authInfo as SdkAuthInfo)?.transport;
transport?.setInitSessionPayload(initPayload);
}

/**
* Validates that the client's protocol version is a valid date string format.
* Per MCP spec, older versions should be accepted if supported - version negotiation
Expand Down Expand Up @@ -103,23 +124,16 @@ export default function initializeRequestHandler({
ctx.authInfo.sessionIdPayload.platformType = finalPlatform;
}

const initPayload = {
clientName,
clientVersion,
supportsElicitation: clientSupportsElicitation,
...(finalPlatform && { platformType: finalPlatform }),
};

// Persist to session cache so subsequent requests can access client info
// This is critical for HTTP transports where sessions are parsed from encrypted headers
updateSessionPayload(sessionId, initPayload);

// Persist initialization data on the transport adapter instance.
// This ensures the data survives across SSE requests (fresh HTTP sessions)
// and works in distributed environments (Vercel Edge, Cloudflare Workers)
// where the encrypted session payload carries the correct initialization state.
const transport = (ctx.authInfo as SdkAuthInfo)?.transport;
transport?.setInitSessionPayload(initPayload);
persistInitPayload(
sessionId,
{
clientName,
clientVersion,
supportsElicitation: clientSupportsElicitation,
...(finalPlatform && { platformType: finalPlatform }),
},
ctx,
);
}
} else if (ctx.authInfo?.sessionIdPayload) {
// Update platform and elicitation support even without client info
Expand All @@ -128,17 +142,14 @@ export default function initializeRequestHandler({
ctx.authInfo.sessionIdPayload.platformType = detectedPlatform;
}

const initPayload = {
supportsElicitation: clientSupportsElicitation,
...(detectedPlatform && { platformType: detectedPlatform }),
};

// Persist to session cache
updateSessionPayload(sessionId, initPayload);

// Persist on transport adapter instance
const transport = (ctx.authInfo as SdkAuthInfo)?.transport;
transport?.setInitSessionPayload(initPayload);
persistInitPayload(
sessionId,
{
supportsElicitation: clientSupportsElicitation,
...(detectedPlatform && { platformType: detectedPlatform }),
},
ctx,
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/generate-schema-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function getZodTypeName(schema) {
*/
/** Stringify a JS value using single quotes for strings (prettier-compatible). */
function toTsLiteral(value) {
if (typeof value === 'string') return `'${value.replace(/'/g, "\\'")}'`;
if (typeof value === 'string') return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
return JSON.stringify(value);
}

Expand Down
Loading