68d02281 by Ean Schuessler

Implement MCP 2025-06-18 specification compliance

- Replace cookie-based session with Mcp-Session-Id header per MCP spec
- Add MCP-Protocol-Version header validation (supports 2025-06-18 only)
- Require Mcp-Session-Id header for non-initialize requests per spec
- Set Mcp-Session-Id response header during initialization
- Update CORS headers to include MCP-specific headers

This ensures full compliance with MCP Streamable HTTP transport specification:
- Proper session management via headers instead of cookies
- Protocol version negotiation and validation
- Session ID validation for security
- Standards-compliant header handling
1 parent 959cd392
...@@ -597,9 +597,34 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -597,9 +597,34 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
597 return 597 return
598 } 598 }
599 599
600 // Validate MCP protocol version per specification
601 String protocolVersion = request.getHeader("MCP-Protocol-Version")
602 if (protocolVersion && protocolVersion != "2025-06-18") {
603 response.setStatus(HttpServletResponse.SC_BAD_REQUEST)
604 response.setContentType("application/json")
605 response.writer.write(groovy.json.JsonOutput.toJson([
606 jsonrpc: "2.0",
607 error: [code: -32600, message: "Unsupported MCP protocol version: ${protocolVersion}. Supported: 2025-06-18"],
608 id: null
609 ]))
610 return
611 }
612
600 // Get session ID from Mcp-Session-Id header per MCP specification 613 // Get session ID from Mcp-Session-Id header per MCP specification
601 String sessionId = request.getHeader("Mcp-Session-Id") 614 String sessionId = request.getHeader("Mcp-Session-Id")
602 615
616 // Validate session ID for non-initialize requests per MCP spec
617 if (!sessionId && rpcRequest.method != "initialize") {
618 response.setStatus(HttpServletResponse.SC_BAD_REQUEST)
619 response.setContentType("application/json")
620 response.writer.write(groovy.json.JsonOutput.toJson([
621 jsonrpc: "2.0",
622 error: [code: -32600, message: "Mcp-Session-Id header required for non-initialize requests"],
623 id: rpcRequest.id
624 ]))
625 return
626 }
627
603 // Process MCP method using Moqui services with session ID if available 628 // Process MCP method using Moqui services with session ID if available
604 def result = processMcpMethod(rpcRequest.method, rpcRequest.params, ec, sessionId) 629 def result = processMcpMethod(rpcRequest.method, rpcRequest.params, ec, sessionId)
605 630
...@@ -613,9 +638,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}") ...@@ -613,9 +638,9 @@ logger.info("Handling Enhanced SSE connection from ${request.remoteAddr}")
613 response.setContentType("application/json") 638 response.setContentType("application/json")
614 response.setCharacterEncoding("UTF-8") 639 response.setCharacterEncoding("UTF-8")
615 640
616 // Set session cookie if result contains sessionId 641 // Set Mcp-Session-Id header if result contains sessionId (per MCP 2025-06-18 spec)
617 if (rpcResponse.result?.sessionId) { 642 if (rpcResponse.result?.sessionId) {
618 response.setHeader("Set-Cookie", "MCP-SESSION=${rpcResponse.result.sessionId}; Path=/; HttpOnly; SameSite=Lax") 643 response.setHeader("Mcp-Session-Id", rpcResponse.result.sessionId)
619 } 644 }
620 645
621 response.writer.write(groovy.json.JsonOutput.toJson(rpcResponse)) 646 response.writer.write(groovy.json.JsonOutput.toJson(rpcResponse))
......