Skip to content

Feat/streamable http transport#6

Merged
dadadadas111 merged 12 commits intomasterfrom
feat/streamable-http-transport
Mar 2, 2026
Merged

Feat/streamable http transport#6
dadadadas111 merged 12 commits intomasterfrom
feat/streamable-http-transport

Conversation

@dadadadas111
Copy link
Owner

@dadadadas111 dadadadas111 commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Added support for HTTP DELETE requests in CORS configuration.
    • Enabled mcp-session-id header support for improved session management and tracking.
    • Session identifiers now exposed in API responses for client reference.
  • Chores

    • Updated staging environment configuration.
    • Removed outdated documentation files.

dadadadas111 and others added 11 commits February 27, 2026 15:52
Ultraworked with Sisyphus (oh-my-opencode).
Add @global RedisModule with ioredis provider (retry strategy, graceful disconnect), Redis constants (REDIS_CLIENT token, key prefixes), and RedisSessionRepository for session CRUD with TTL and stale SET member pruning. Add RedisSessionData interface to session DTO.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Rewrite SessionManagerService to use Redis-backed metadata with local McpServer cache. Add await to async session calls in McpController. Wire RedisSessionRepository into McpModule. Import RedisModule in AppModule. Update .env.example with required Redis vars.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add Redis 7-alpine service to production (docker-compose.yml) and staging (docker-compose.staging.yml) with persistent volumes, Redis env vars, depends_on, and MCP_SESSION_TTL configuration.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add unit tests for RedisSessionRepository (mock ioredis, CRUD, stale pruning, stats) and SessionManagerService (mock repository + factory, session lifecycle, cache miss recovery).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add deploy.sh (unified VPS deploy with backup/rollback for prod+staging). Rewrite AGENT.md with full architecture, module map, data flow, and conventions. Create AGENTS.md (hierarchical subagent knowledge). Create docs/DEPLOYMENT.md (ops runbook). Move redis-plan.md to docs/feature-plan/.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Convert all CRLF line endings to LF across the codebase for consistency.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Update tool-registry to extract Bearer token from extra.authInfo instead of extra.sessionId. Add optional existingSessionId parameter to session-manager for SDK-managed session IDs.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ansport

Replace hand-rolled JSON-RPC dispatch with official @modelcontextprotocol/sdk StreamableHTTPServerTransport. Adds POST/GET/DELETE handlers for full MCP Streamable HTTP spec compliance, enabling n8n and other SDK-compliant clients to connect. Remove McpProtocolHandlerService from module (superseded by SDK transport).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add DELETE to CORS methods, mcp-session-id to allowed/exposed headers for SDK transport. Fix staging docker-compose PORT default from 3001 to 3002 to match host mapping.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Remove TESTING_RESOURCES.md and docs/IMPLEMENTATION-SUMMARY.md that are no longer relevant after the StreamableHTTPServerTransport refactor.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

This pull request refactors the MCP controller architecture from per-request protocol handler design to streaming HTTP transport-based session management. Changes include CORS and configuration updates, removal of documentation files, session manager enhancements with optional session ID support, and conversion of tool registry authorization from session ID to Bearer token format.

Changes

Cohort / File(s) Summary
Documentation Cleanup
TESTING_RESOURCES.md, docs/IMPLEMENTATION-SUMMARY.md
Removed testing guide and implementation overview documentation files (562 lines total deleted).
Infrastructure Configuration
docker-compose.staging.yml, src/main.ts
Updated Docker Compose staging environment PORT from 3001 to 3002; expanded CORS to allow DELETE method, added mcp-session-id header support, and exposed Mcp-Session-Id response header.
MCP Architecture Refactor
src/mcp/mcp.controller.ts, src/mcp/mcp.module.ts
Replaced protocol handler design with StreamableHTTPServerTransport-based approach. Removed handleMcpRequest and added handleMcpPost, handleMcpGet, handleMcpDelete handlers; introduced validateAuth method and internal transport state management. Removed McpProtocolHandlerService from module exports.
Session & Authentication Updates
src/mcp/services/session-manager.service.ts, src/tools/services/tool-registry.service.ts
Enhanced createSession with optional existingSessionId parameter; converted tool registry authorization headers from session ID format to Bearer token format (derived from authInfo.token).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant McpController
    participant SessionManager
    participant ServerFactory
    participant Transport
    participant Redis

    Note over Client,Redis: OLD FLOW: Per-Request Protocol Handler
    Client->>McpController: POST /mcp/:projectApiKey (handleMcpRequest)
    McpController->>ServerFactory: Create new server per request
    McpController->>Transport: Protocol handler creates transport
    Transport->>ServerFactory: Handle request
    ServerFactory->>Redis: Lookup/store session
    Transport-->>Client: Response
    Note over Transport: Transport discarded after response

    Note over Client,Redis: NEW FLOW: Persistent Transport Sessions
    Client->>McpController: POST /mcp/:projectApiKey (initialize)
    McpController->>McpController: validateAuth (JWT extraction)
    McpController->>SessionManager: createSession with existingSessionId
    SessionManager->>Redis: Persist session metadata
    McpController->>ServerFactory: Create server
    ServerFactory->>Transport: Create StreamableHTTPServerTransport
    Transport->>McpController: Register transport instance
    Transport-->>Client: Session established with mcp-session-id

    Client->>McpController: GET /mcp/:projectApiKey (with mcp-session-id)
    McpController->>McpController: Lookup transport by session ID
    Transport->>ServerFactory: Route to existing server
    ServerFactory-->>Client: Streaming response (SSE-like)

    Client->>McpController: DELETE /mcp/:projectApiKey (with mcp-session-id)
    McpController->>Transport: Close transport
    Transport->>SessionManager: Cleanup session
    SessionManager->>Redis: Remove session
    Transport-->>Client: 200 OK (cleanup complete)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Feat/mcp for dashboard users #2: Overlapping changes to MCP controller and session-based transport architecture, including mcp_v2/session store and controller initialization patterns.
  • Feat/mcp #4: Modifies same files (src/mcp/mcp.controller.ts, src/mcp/services/session-manager.service.ts) with related endpoint handlers and session lifecycle changes.
  • docs: add Redis session store implementation plan #5: Updates MCP session handling surface, including SessionManagerService signatures and session creation/lookup semantics.

Poem

🐰 Transport streams now flow so clean,
No handlers spun on every scene,
Sessions persist through Redis light,
Bearer tokens shine so bright,
The gateway's course is set just right! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/streamable http transport' directly reflects the main architectural change: replacing per-request server creation with persistent streaming HTTP transports. This is the primary focus across multiple modified files (mcp.controller.ts, mcp.module.ts, tool-registry.service.ts) and aligns with the core objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/streamable-http-transport

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
docker-compose.staging.yml (1)

8-12: Decouple host port and app port variables to avoid override mismatches.

Using one PORT variable for both host binding and in-container app port can break routing if someone overrides it (container mapping target stays fixed unless updated too). Consider separate variables (HOST_PORT, APP_PORT) for safer staging overrides.

Suggested compose tweak
 services:
   iot-cloud-mcp-staging:
@@
     ports:
-      - '${PORT:-3002}:3002'
+      - '${HOST_PORT:-3002}:${APP_PORT:-3002}'
@@
-      PORT: ${PORT:-3002}
+      PORT: ${APP_PORT:-3002}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.staging.yml` around lines 8 - 12, Decouple the
host-to-container port binding by introducing HOST_PORT and APP_PORT instead of
reusing PORT: change the port mapping that currently reads '-
'${PORT:-3002}:3002'' to use HOST_PORT and APP_PORT (e.g. host binding
'${HOST_PORT:-3002}:${APP_PORT:-3002}' or at minimum '${HOST_PORT:-3002}:3002'),
and update the environment entry that currently reads 'PORT: ${PORT:-3002}' to
use APP_PORT (e.g. 'PORT: ${APP_PORT:-3002}' or add 'APP_PORT:
${APP_PORT:-3002}') so the container's internal app port and the host binding
are independent (refer to the existing symbols PORT, '- '${PORT:-3002}:3002'',
and 'PORT: ${PORT:-3002}').
src/tools/services/tool-registry.service.ts (1)

51-51: Extract Bearer header composition into a single helper.

The same authorization expression is duplicated across all tool registrations, which makes future auth changes easy to miss in one or more tools.

♻️ Suggested refactor
+    const buildAuthorization = (extra: { authInfo?: { token?: string } }) =>
+      extra.authInfo?.token ? `Bearer ${extra.authInfo.token}` : '';
+
     // Register fetchUser tool
     mcpServer.registerTool(
       FETCH_USER_TOOL.name,
@@
       async (params: Record<string, unknown>, extra) => {
         return this.toolExecutor.executeTool(FETCH_USER_TOOL.name, params, {
-          authorization: extra.authInfo?.token ? `Bearer ${extra.authInfo.token}` : '',
+          authorization: buildAuthorization(extra),
           projectApiKey,
           meta: extra as Record<string, unknown>,
         });
       },
     );

Also applies to: 67-67, 83-83, 99-99, 115-115, 131-131, 147-147, 163-163, 179-179, 195-195, 211-211, 227-227, 243-243, 259-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/services/tool-registry.service.ts` at line 51, The repeated inline
expression `extra.authInfo?.token ? \`Bearer ${extra.authInfo.token}\` : ''`
used for the authorization header across registrations should be extracted into
a single helper function (e.g., `composeBearerToken(extra)` or
`getAuthorizationHeader(extra: ExtraType)`) inside ToolRegistryService (or
nearby in src/tools/services/tool-registry.service.ts); implement the helper to
return the exact header string and replace every inline occurrence (the
authorization property used in each tool registration) with a call to that
helper so all registrations (previously at the duplicated lines) use the
centralized function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcp/mcp.controller.ts`:
- Around line 82-87: The code currently calls decodeJwt(token) and trusts
decoded.sub as the authenticated identity (decoded, userId) without
cryptographic verification; change this to perform full JWT verification
(signature, expiry/exp, issuer/iss and audience/aud) before using sub by
replacing decodeJwt with a verification call (e.g., verifyJwt or jwt.verify) or
invoking your AuthService.verifyToken, check returned payload is valid and not
expired, and only then extract userId from payload.sub; also handle verification
errors explicitly and reject tokens that fail signature or claim checks to
prevent impersonation.
- Around line 163-170: The code creates two different McpServer instances for
the same session (one passed to sessionManager.createSession and another later
used to connect the transport), which can desync server state; modify
onsessioninitialized so you create a single server via
serverFactory.createServer(projectApiKey) and reuse that same McpServer instance
for both sessionManager.createSession(...) and the transport connection (where
transport.connect/transport.setServer or equivalent is called), removing the
second serverFactory.createServer call and any duplicate server creation; update
references to that single variable in onsessioninitialized and any helper
methods so the session store and active transport share the exact same McpServer
object.
- Around line 143-145: When reusing or accepting a transport/session ID in the
MCP controller (places using mcpSessionId, this.transports, and
this.sessionProjectMap — e.g., the reuse check at the start of the POST handler
and the analogous checks in the GET and DELETE flows), require that
this.sessionProjectMap.get(mcpSessionId) === projectApiKey before allowing
reuse; if the mapping is absent or mismatched, treat the session as invalid and
do not return or reuse the transport. Also ensure that when creating a new
session you set this.sessionProjectMap.set(newSessionId, projectApiKey) so
future lookups enforce project binding. This change should be applied to the
checks around mcpSessionId at the three mentioned code sites.
- Around line 148-152: Add a proper module augmentation for Node's
IncomingMessage so you can attach auth without unsafe casts: create/update a
.d.ts types file that declares module 'http' { interface IncomingMessage {
auth?: AuthInfo } } then remove all occurrences of the double-cast (e.g., the
assignments and calls around req.auth and transport.handleRequest in
mcp.controller.ts) and use req.auth = authInfo and pass req directly to
transport.handleRequest and other call sites (refer to IncomingMessage, auth,
AuthInfo, and transport.handleRequest to locate and update all instances).

In `@src/mcp/services/session-manager.service.ts`:
- Around line 48-50: The createSession method currently trusts existingSessionId
blindly; validate existingSessionId in createSession (in
session-manager.service.ts) before using it as sessionId: only accept it if it
is a non-empty string and passes a UUID-v4 validation (use the uuid library's
validate/isUuid helper or a strict regex); if validation fails, discard it and
generate a new id via uuidv4(); optionally emit a warning log when an invalid
existingSessionId is provided to aid debugging.

---

Nitpick comments:
In `@docker-compose.staging.yml`:
- Around line 8-12: Decouple the host-to-container port binding by introducing
HOST_PORT and APP_PORT instead of reusing PORT: change the port mapping that
currently reads '- '${PORT:-3002}:3002'' to use HOST_PORT and APP_PORT (e.g.
host binding '${HOST_PORT:-3002}:${APP_PORT:-3002}' or at minimum
'${HOST_PORT:-3002}:3002'), and update the environment entry that currently
reads 'PORT: ${PORT:-3002}' to use APP_PORT (e.g. 'PORT: ${APP_PORT:-3002}' or
add 'APP_PORT: ${APP_PORT:-3002}') so the container's internal app port and the
host binding are independent (refer to the existing symbols PORT, '-
'${PORT:-3002}:3002'', and 'PORT: ${PORT:-3002}').

In `@src/tools/services/tool-registry.service.ts`:
- Line 51: The repeated inline expression `extra.authInfo?.token ? \`Bearer
${extra.authInfo.token}\` : ''` used for the authorization header across
registrations should be extracted into a single helper function (e.g.,
`composeBearerToken(extra)` or `getAuthorizationHeader(extra: ExtraType)`)
inside ToolRegistryService (or nearby in
src/tools/services/tool-registry.service.ts); implement the helper to return the
exact header string and replace every inline occurrence (the authorization
property used in each tool registration) with a call to that helper so all
registrations (previously at the duplicated lines) use the centralized function.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffc6d66 and ec84f9f.

📒 Files selected for processing (8)
  • TESTING_RESOURCES.md
  • docker-compose.staging.yml
  • docs/IMPLEMENTATION-SUMMARY.md
  • src/main.ts
  • src/mcp/mcp.controller.ts
  • src/mcp/mcp.module.ts
  • src/mcp/services/session-manager.service.ts
  • src/tools/services/tool-registry.service.ts
💤 Files with no reviewable changes (2)
  • TESTING_RESOURCES.md
  • docs/IMPLEMENTATION-SUMMARY.md

Comment on lines +82 to +87
const decoded = decodeJwt(token);
if (!decoded || !(decoded.sub as string)) {
throw new Error('Invalid token payload');
}
const userId = decoded.sub as string;
this.logger.debug(`Token decoded - UserId: ${userId}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not treat decoded JWT payload as authenticated identity.

Line 82 only decodes token payload and then trusts sub (Line 86). Without cryptographic verification (signature, issuer, audience, expiry), forged tokens can impersonate any user.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp.controller.ts` around lines 82 - 87, The code currently calls
decodeJwt(token) and trusts decoded.sub as the authenticated identity (decoded,
userId) without cryptographic verification; change this to perform full JWT
verification (signature, expiry/exp, issuer/iss and audience/aud) before using
sub by replacing decodeJwt with a verification call (e.g., verifyJwt or
jwt.verify) or invoking your AuthService.verifyToken, check returned payload is
valid and not expired, and only then extract userId from payload.sub; also
handle verification errors explicitly and reject tokens that fail signature or
claim checks to prevent impersonation.

Comment on lines +143 to +145
if (mcpSessionId && this.transports.has(mcpSessionId)) {
const transport = this.transports.get(mcpSessionId)!;
this.logger.debug(`Reusing transport for session: ${mcpSessionId}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Enforce project binding for every session ID lookup.

At Line 143, Line 239, and Line 284, validation only checks transport existence. A valid session ID from another project can be reused against a different :projectApiKey. Enforce this.sessionProjectMap.get(sessionId) === projectApiKey before allowing POST/GET/DELETE flows.

🔒 Suggested fix
+  private isSessionBoundToProject(sessionId: string, projectApiKey: string): boolean {
+    return this.sessionProjectMap.get(sessionId) === projectApiKey;
+  }
+
   // Existing session — reuse transport
-  if (mcpSessionId && this.transports.has(mcpSessionId)) {
+  if (
+    mcpSessionId &&
+    this.transports.has(mcpSessionId) &&
+    this.isSessionBoundToProject(mcpSessionId, projectApiKey)
+  ) {
@@
-  if (!mcpSessionId || !this.transports.has(mcpSessionId)) {
+  if (
+    !mcpSessionId ||
+    !this.transports.has(mcpSessionId) ||
+    !this.isSessionBoundToProject(mcpSessionId, projectApiKey)
+  ) {
@@
-  if (!mcpSessionId || !this.transports.has(mcpSessionId)) {
+  if (
+    !mcpSessionId ||
+    !this.transports.has(mcpSessionId) ||
+    !this.isSessionBoundToProject(mcpSessionId, projectApiKey)
+  ) {
Based on learnings: Multi-tenant support: each project has isolated API key embedded in URL path (`/mcp/:projectApiKey`), with sessions per-project per-user.

Also applies to: 239-247, 284-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp.controller.ts` around lines 143 - 145, When reusing or accepting
a transport/session ID in the MCP controller (places using mcpSessionId,
this.transports, and this.sessionProjectMap — e.g., the reuse check at the start
of the POST handler and the analogous checks in the GET and DELETE flows),
require that this.sessionProjectMap.get(mcpSessionId) === projectApiKey before
allowing reuse; if the mapping is absent or mismatched, treat the session as
invalid and do not return or reuse the transport. Also ensure that when creating
a new session you set this.sessionProjectMap.set(newSessionId, projectApiKey) so
future lookups enforce project binding. This change should be applied to the
checks around mcpSessionId at the three mentioned code sites.

Comment on lines +148 to +152
(req as unknown as IncomingMessage & { auth?: AuthInfo }).auth = authInfo;
await transport.handleRequest(
req as unknown as IncomingMessage & { auth?: AuthInfo },
res as unknown as ServerResponse,
req.body,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Run the provided verification script
rg -n --type=ts '\bas\s+unknown\s+as\b' src/mcp/mcp.controller.ts

Repository: dadadadas111/iot-cloud-mcp

Length of output: 752


🏁 Script executed:

#!/bin/bash
# Read the controller file with context around each assertion cluster
echo "=== Lines 140-160 (around 148-151) ==="
sed -n '140,160p' src/mcp/mcp.controller.ts

echo -e "\n=== Lines 180-200 (around 190-193) ==="
sed -n '180,200p' src/mcp/mcp.controller.ts

echo -e "\n=== Lines 240-260 (around 252-255) ==="
sed -n '240,260p' src/mcp/mcp.controller.ts

echo -e "\n=== Lines 290-310 (around 298-299) ==="
sed -n '290,310p' src/mcp/mcp.controller.ts

echo -e "\n=== File imports and type definitions (top 60 lines) ==="
head -60 src/mcp/mcp.controller.ts

Repository: dadadadas111/iot-cloud-mcp

Length of output: 4886


🏁 Script executed:

#!/bin/bash
# Search for existing module augmentations of IncomingMessage
rg -n 'declare\s+module\s+.*http' --type=ts src/

echo "---"

# Search for interface augmentations of IncomingMessage
rg -n 'interface\s+IncomingMessage' --type=ts src/

echo "---"

# Check if AuthInfo type is already being extended anywhere
rg -n 'AuthInfo' --type=ts src/ | head -20

Repository: dadadadas111/iot-cloud-mcp

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Check if there are any type definition files or interfaces related to StreamableHTTPServerTransport
fd -e d.ts -e ts --path-separator '/' . node_modules/@modelcontextprotocol/sdk/server/ 2>/dev/null | head -10

# Alternative: search for how handleRequest is defined in types
rg -n 'handleRequest' --type=ts src/mcp/ -A 3 | head -30

Repository: dadadadas111/iot-cloud-mcp

Length of output: 1522


Remove as unknown as casts by augmenting the IncomingMessage interface type.

The double-casts on lines 148, 150–151, 190, 192–193, 252, 254–255, and 297–299 bypass TypeScript's type system to attach the auth property to incoming requests. Instead of unsafe assertions, use module augmentation to properly extend IncomingMessage:

Create or update a type definitions file with:

declare module 'http' {
  interface IncomingMessage {
    auth?: AuthInfo;
  }
}

This eliminates all unsafe casts and aligns with the coding guideline: "Never use as any, @ts-ignore, or type assertions that circumvent TypeScript's type system."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp.controller.ts` around lines 148 - 152, Add a proper module
augmentation for Node's IncomingMessage so you can attach auth without unsafe
casts: create/update a .d.ts types file that declares module 'http' { interface
IncomingMessage { auth?: AuthInfo } } then remove all occurrences of the
double-cast (e.g., the assignments and calls around req.auth and
transport.handleRequest in mcp.controller.ts) and use req.auth = authInfo and
pass req directly to transport.handleRequest and other call sites (refer to
IncomingMessage, auth, AuthInfo, and transport.handleRequest to locate and
update all instances).

Comment on lines +163 to +170
onsessioninitialized: async (sessionId: string) => {
// Store transport and project mapping
this.transports.set(sessionId, transport);
this.sessionProjectMap.set(sessionId, projectApiKey);

// Persist session to Redis
const server = this.serverFactory.createServer(projectApiKey);
await this.sessionManager.createSession(projectApiKey, userId, server, sessionId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Create only one McpServer per initialized session.

Line 169 creates a server for createSession, while Line 176 creates another server and connects that one to transport. This can desync cached session server state from the active transport server.

🧩 Suggested fix
-      const transport = new StreamableHTTPServerTransport({
+      const server = this.serverFactory.createServer(projectApiKey);
+      const transport = new StreamableHTTPServerTransport({
         sessionIdGenerator: () => randomUUID(),
         onsessioninitialized: async (sessionId: string) => {
@@
-          const server = this.serverFactory.createServer(projectApiKey);
           await this.sessionManager.createSession(projectApiKey, userId, server, sessionId);
           this.logger.log(`Session initialized - SessionId: ${sessionId}, UserId: ${userId}`);
         },
       });
@@
-      const server = this.serverFactory.createServer(projectApiKey);
       await server.connect(transport);

Also applies to: 176-177

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/mcp.controller.ts` around lines 163 - 170, The code creates two
different McpServer instances for the same session (one passed to
sessionManager.createSession and another later used to connect the transport),
which can desync server state; modify onsessioninitialized so you create a
single server via serverFactory.createServer(projectApiKey) and reuse that same
McpServer instance for both sessionManager.createSession(...) and the transport
connection (where transport.connect/transport.setServer or equivalent is
called), removing the second serverFactory.createServer call and any duplicate
server creation; update references to that single variable in
onsessioninitialized and any helper methods so the session store and active
transport share the exact same McpServer object.

Comment on lines +48 to 50
async createSession(projectApiKey: string, userId: string, server: McpServer, existingSessionId?: string): Promise<string> {
const sessionId = existingSessionId || uuidv4();
const now = new Date().toISOString();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate existingSessionId before persisting.

Line 49 currently accepts any non-undefined value path, so malformed IDs can enter Redis/cache keyspace and make session behavior harder to reason about.

🛡️ Suggested fix
+  private static readonly UUID_V4_REGEX =
+    /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+
   async createSession(projectApiKey: string, userId: string, server: McpServer, existingSessionId?: string): Promise<string> {
-    const sessionId = existingSessionId || uuidv4();
+    if (existingSessionId !== undefined && !SessionManagerService.UUID_V4_REGEX.test(existingSessionId)) {
+      throw new Error('Invalid existingSessionId format');
+    }
+    const sessionId = existingSessionId ?? uuidv4();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp/services/session-manager.service.ts` around lines 48 - 50, The
createSession method currently trusts existingSessionId blindly; validate
existingSessionId in createSession (in session-manager.service.ts) before using
it as sessionId: only accept it if it is a non-empty string and passes a UUID-v4
validation (use the uuid library's validate/isUuid helper or a strict regex); if
validation fails, discard it and generate a new id via uuidv4(); optionally emit
a warning log when an invalid existingSessionId is provided to aid debugging.

@dadadadas111 dadadadas111 merged commit f7c120d into master Mar 2, 2026
6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant