1c226c77 by Ean Schuessler

Continued effort to rough in a notifications bus

1 parent 6bbfd372
...@@ -312,8 +312,41 @@ try { ...@@ -312,8 +312,41 @@ try {
312 private void handleSseConnection(HttpServletRequest request, HttpServletResponse response, ExecutionContextImpl ec, String webappName) 312 private void handleSseConnection(HttpServletRequest request, HttpServletResponse response, ExecutionContextImpl ec, String webappName)
313 throws IOException { 313 throws IOException {
314 314
315 logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") 315 logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
316 316
317 // Check for existing session ID first
318 String sessionId = request.getHeader("Mcp-Session-Id")
319 def visit = null
320
321 // If we have a session ID, try to find existing Visit
322 if (sessionId) {
323 try {
324 visit = ec.entity.find("moqui.server.Visit")
325 .condition("visitId", sessionId)
326 .disableAuthz()
327 .one()
328
329 if (visit) {
330 // Verify user has access to this Visit
331 if (!visit.userId || !ec.user.userId || visit.userId.toString() != ec.user.userId.toString()) {
332 logger.warn("Visit userId ${visit.userId} doesn't match current user userId ${ec.user.userId} - access denied")
333 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied for session: " + sessionId)
334 return
335 }
336
337 // Set existing visit ID in HTTP session
338 request.session.setAttribute("moqui.visitId", sessionId)
339 logger.info("Reusing existing Visit ${sessionId} for user ${ec.user.username}")
340 } else {
341 logger.warn("Session ID ${sessionId} not found, will create new Visit")
342 }
343 } catch (Exception e) {
344 logger.warn("Error looking up existing session ${sessionId}: ${e.message}")
345 }
346 }
347
348 // Only create new Visit if we didn't find an existing one
349 if (!visit) {
317 // Initialize web facade for Visit creation, but avoid screen resolution 350 // Initialize web facade for Visit creation, but avoid screen resolution
318 // Modify request path to avoid ScreenResourceNotFoundException 351 // Modify request path to avoid ScreenResourceNotFoundException
319 String originalRequestURI = request.getRequestURI() 352 String originalRequestURI = request.getRequestURI()
...@@ -321,8 +354,6 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -321,8 +354,6 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
321 request.setAttribute("javax.servlet.include.request_uri", "/mcp") 354 request.setAttribute("javax.servlet.include.request_uri", "/mcp")
322 request.setAttribute("javax.servlet.include.path_info", "") 355 request.setAttribute("javax.servlet.include.path_info", "")
323 356
324 def visit = null
325
326 try { 357 try {
327 ec.initWebFacade(webappName, request, response) 358 ec.initWebFacade(webappName, request, response)
328 // Web facade was successful, get the Visit it created 359 // Web facade was successful, get the Visit it created
...@@ -330,6 +361,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -330,6 +361,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
330 if (!visit) { 361 if (!visit) {
331 throw new Exception("Web facade succeeded but no Visit created") 362 throw new Exception("Web facade succeeded but no Visit created")
332 } 363 }
364 logger.info("Created new Visit ${visit.visitId} for user ${ec.user.username}")
333 } catch (Exception e) { 365 } catch (Exception e) {
334 logger.warn("Web facade initialization failed: ${e.message}, trying manual Visit creation") 366 logger.warn("Web facade initialization failed: ${e.message}, trying manual Visit creation")
335 // Try to create Visit manually using the same pattern as UserFacadeImpl 367 // Try to create Visit manually using the same pattern as UserFacadeImpl
...@@ -379,6 +411,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -379,6 +411,7 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
379 return 411 return
380 } 412 }
381 } 413 }
414 }
382 415
383 // Final check that we have a Visit 416 // Final check that we have a Visit
384 if (!visit) { 417 if (!visit) {
...@@ -406,7 +439,14 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -406,7 +439,14 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
406 VisitBasedMcpSession session = new VisitBasedMcpSession(visit, response.writer, ec) 439 VisitBasedMcpSession session = new VisitBasedMcpSession(visit, response.writer, ec)
407 440
408 try { 441 try {
409 // Send initial connection event 442 // Check if this is old HTTP+SSE transport (no session ID, no prior initialization)
443 // Send endpoint event first for backwards compatibility
444 if (!request.getHeader("Mcp-Session-Id")) {
445 logger.info("No Mcp-Session-Id header detected, assuming old HTTP+SSE transport")
446 sendSseEvent(response.writer, "endpoint", "/mcp", 0)
447 }
448
449 // Send initial connection event for new transport
410 def connectData = [ 450 def connectData = [
411 version: "2.0.2", 451 version: "2.0.2",
412 protocolVersion: "2025-06-18", 452 protocolVersion: "2025-06-18",
...@@ -415,11 +455,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -415,11 +455,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
415 455
416 // Set MCP session ID header per specification BEFORE sending any data 456 // Set MCP session ID header per specification BEFORE sending any data
417 response.setHeader("Mcp-Session-Id", visit.visitId.toString()) 457 response.setHeader("Mcp-Session-Id", visit.visitId.toString())
458 logger.info("Set Mcp-Session-Id header to ${visit.visitId} for SSE connection")
418 459
419 sendSseEvent(response.writer, "connect", JsonOutput.toJson(connectData), 0) 460 sendSseEvent(response.writer, "connect", JsonOutput.toJson(connectData), 1)
420
421 // Send endpoint info for message posting (for compatibility)
422 sendSseEvent(response.writer, "endpoint", "/mcp", 1)
423 461
424 // Keep connection alive with periodic pings 462 // Keep connection alive with periodic pings
425 int pingCount = 0 463 int pingCount = 0
...@@ -849,10 +887,19 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -849,10 +887,19 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
849 887
850 // Set Mcp-Session-Id header BEFORE any response data (per MCP 2025-06-18 spec) 888 // Set Mcp-Session-Id header BEFORE any response data (per MCP 2025-06-18 spec)
851 // For initialize method, always use sessionId we have (from visit or header) 889 // For initialize method, always use sessionId we have (from visit or header)
890 String responseSessionId = null
852 if (rpcRequest.method == "initialize" && sessionId) { 891 if (rpcRequest.method == "initialize" && sessionId) {
853 response.setHeader("Mcp-Session-Id", sessionId.toString()) 892 responseSessionId = sessionId.toString()
854 } else if (result?.sessionId) { 893 } else if (result?.sessionId) {
855 response.setHeader("Mcp-Session-Id", result.sessionId.toString()) 894 responseSessionId = result.sessionId.toString()
895 } else if (sessionId) {
896 // For other methods, ensure we always return the session ID from header
897 responseSessionId = sessionId.toString()
898 }
899
900 if (responseSessionId) {
901 response.setHeader("Mcp-Session-Id", responseSessionId)
902 logger.info("Set Mcp-Session-Id header to ${responseSessionId} for method ${rpcRequest.method}")
856 } 903 }
857 904
858 // Build JSON-RPC response for regular requests 905 // Build JSON-RPC response for regular requests
...@@ -910,9 +957,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -910,9 +957,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
910 // For initialize, use the visitId we just created instead of null sessionId from request 957 // For initialize, use the visitId we just created instead of null sessionId from request
911 if (visit && visit.visitId) { 958 if (visit && visit.visitId) {
912 params.sessionId = visit.visitId 959 params.sessionId = visit.visitId
913 // Set session to initializing state using the header sessionId as key (for consistency) 960 // Set session to initializing state using actual sessionId as key (for consistency)
914 sessionStates.put(sessionId, STATE_INITIALIZING) 961 sessionStates.put(params.sessionId, STATE_INITIALIZING)
915 logger.info("Initialize - using visitId: ${visit.visitId}, set state ${sessionId} to INITIALIZING") 962 logger.info("Initialize - using visitId: ${visit.visitId}, set state ${params.sessionId} to INITIALIZING")
916 } else { 963 } else {
917 logger.warn("Initialize - no visit available, using null sessionId") 964 logger.warn("Initialize - no visit available, using null sessionId")
918 } 965 }
......