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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mcp-framework",
"version": "0.2.3",
"version": "0.2.4",

"description": "Framework for building Model Context Protocol (MCP) servers in Typescript",
"type": "module",
Expand Down
31 changes: 25 additions & 6 deletions src/transports/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,32 @@ export class HttpStreamTransport extends AbstractTransport {
logger.debug(`Handling GET request to ${this._config.endpoint}`);
const acceptHeader = req.headers.accept || '';
if (!acceptHeader.includes(SSE_CONTENT_TYPE) && !acceptHeader.includes('*/*')) throw this.httpError(406, `Not Acceptable: GET requires Accept header including ${SSE_CONTENT_TYPE}`);

const sessionIdHeader = getRequestHeader(req.headers, this._config.session.headerName);
let session: SessionData | undefined;
if (this._config.session.enabled) { session = this.validateSession(sessionIdHeader, req, true); session.lastActivity = Date.now(); }
await this.handleAuthentication(req, res, `GET session ${session?.id || 'N/A'}`, session);

if (this._config.session.enabled && sessionIdHeader) {
// If a session ID is provided, validate it
session = this.validateSession(sessionIdHeader, req, false);
session.lastActivity = Date.now();
logger.debug(`Found valid session: ${session.id}`);
await this.handleAuthentication(req, res, `GET session ${session.id}`, session);
} else if (this._config.session.enabled) {
// Allow initial GET requests without session ID during initialization phase
logger.debug(`GET request without session ID - allowing as potential initialization connection`);
await this.handleAuthentication(req, res, `GET initialization`, undefined);
} else {
// Sessions disabled
await this.handleAuthentication(req, res, `GET (sessions disabled)`, undefined);
}

const lastEventId = getRequestHeader(req.headers, "Last-Event-ID");
if (lastEventId && !this._config.resumability.enabled) logger.warn(`Client sent Last-Event-ID (${lastEventId}) but resumability is disabled.`);
if (lastEventId && !this._config.resumability.enabled) {
logger.warn(`Client sent Last-Event-ID (${lastEventId}) but resumability is disabled.`);
}

this.setupSSEConnection(req, res, session?.id, lastEventId);
logger.debug(`Established SSE stream for GET request (Session: ${session?.id || 'N/A'})`);
logger.debug(`Established SSE stream for GET request (Session: ${session?.id || 'initialization phase'})`);
}

private async handleDelete(req: IncomingMessage, res: ServerResponse): Promise<void> {
Expand Down Expand Up @@ -520,8 +538,9 @@ export class HttpStreamTransport extends AbstractTransport {
throw this.httpError(400, `Bad Request: Missing required session header ${headerName}`, -32601, undefined, requestId);
}
else {
logger.error(`Programming error: validateSession called for initialization request with isMandatory=false`);
throw this.httpError(500, "Internal Server Error: Session validation incorrectly called for initialization", -32603, undefined, requestId);
// This is a valid case for initialization or when sessionId is optional
logger.debug(`No session ID provided and not mandatory - acceptable for initialization`);
return undefined as any; // Will be caught by typescript at call site
}
}

Expand Down