Skip to content

Commit 5740a7d

Browse files
Add session ownership validation to Redis transport
- Implement setSessionOwner, getSessionOwner, and validation functions - Add isSessionOwnedBy check combining liveness and ownership - Simplify getShttpTransport by removing cleanup return - Move cleanup logic to transport onclose handler 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 75bcc06 commit 5740a7d

File tree

1 file changed

+24
-33
lines changed

1 file changed

+24
-33
lines changed

src/services/redisTransport.ts

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { redisClient } from "../redis.js";
33
import { Transport, TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js";
44
import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
55
import { JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js";
6-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
76

87
let redisTransportCounter = 0;
98
const notificationStreamId = "__GET_stream";
@@ -60,6 +59,27 @@ export async function isLive(sessionId: string): Promise<boolean> {
6059
return numSubs > 0;
6160
}
6261

62+
export async function setSessionOwner(sessionId: string, userId: string): Promise<void> {
63+
await redisClient.set(`session:${sessionId}:owner`, userId);
64+
}
65+
66+
export async function getSessionOwner(sessionId: string): Promise<string | null> {
67+
return await redisClient.get(`session:${sessionId}:owner`);
68+
}
69+
70+
export async function validateSessionOwnership(sessionId: string, userId: string): Promise<boolean> {
71+
const owner = await getSessionOwner(sessionId);
72+
return owner === userId;
73+
}
74+
75+
export async function isSessionOwnedBy(sessionId: string, userId: string): Promise<boolean> {
76+
const isLiveSession = await isLive(sessionId);
77+
if (!isLiveSession) {
78+
return false;
79+
}
80+
return await validateSessionOwnership(sessionId, userId);
81+
}
82+
6383

6484
export function redisRelayToMcpServer(sessionId: string, transport: Transport): () => Promise<void> {
6585
let redisCleanup: (() => Promise<void>) | undefined = undefined;
@@ -182,45 +202,16 @@ export class ServerRedisTransport implements Transport {
182202
}
183203
}
184204

185-
export async function startServerListeningToRedis(serverWrapper: { server: Server; cleanup: () => void }, sessionId: string): Promise<ServerRedisTransport> {
186-
const serverRedisTransport = new ServerRedisTransport(sessionId);
187-
188-
// Add cleanup callback to the transport
189-
const originalClose = serverRedisTransport.close.bind(serverRedisTransport);
190-
serverRedisTransport.close = async () => {
191-
serverWrapper.cleanup();
192-
await originalClose();
193-
};
194-
195-
// The server.connect() will call start() on the transport
196-
await serverWrapper.server.connect(serverRedisTransport)
197-
198-
return serverRedisTransport;
199-
}
200-
201-
export async function getFirstShttpTransport(sessionId: string): Promise<{shttpTransport: StreamableHTTPServerTransport, cleanup: () => Promise<void>}> {
202-
const shttpTransport = new StreamableHTTPServerTransport({
203-
sessionIdGenerator: () => sessionId,
204-
enableJsonResponse: true, // Enable JSON response mode
205-
});
206-
207-
// Use the new request-id based relay approach
208-
const cleanup = redisRelayToMcpServer(sessionId, shttpTransport);
209-
210-
return { shttpTransport, cleanup };
211-
}
212-
213-
export async function getShttpTransport(sessionId: string): Promise<{shttpTransport: StreamableHTTPServerTransport, cleanup: () => Promise<void>}> {
205+
export async function getShttpTransport(sessionId: string): Promise<StreamableHTTPServerTransport> {
214206
// Giving undefined here and setting the sessionId means the
215207
// transport wont try to create a new session.
216208
const shttpTransport = new StreamableHTTPServerTransport({
217209
sessionIdGenerator: undefined,
218-
enableJsonResponse: true, // Use JSON response mode for all requests
219210
})
220211
shttpTransport.sessionId = sessionId;
221212

222213
// Use the new request-id based relay approach
223214
const cleanup = redisRelayToMcpServer(sessionId, shttpTransport);
224-
225-
return { shttpTransport, cleanup };
215+
shttpTransport.onclose = cleanup;
216+
return shttpTransport;
226217
}

0 commit comments

Comments
 (0)