Refactor MCP implementation with enhanced security and session management
- Replace MoquiMcpServlet with EnhancedMcpServlet for better SSE handling - Add proper JSON-RPC message classes for MCP compatibility - Implement proper permission checks in ToolsList service - Remove temporary permission bypasses and test ping service - Update McpFilter to use EnhancedMcpServlet - Clean up unused dependencies and configuration files - Fix parameter type handling and required field detection
Showing
11 changed files
with
93 additions
and
227 deletions
lib/moqui-framework-3.1.0-rc2.jar
deleted
100644 → 0
No preview for this file type
| ... | @@ -231,7 +231,7 @@ | ... | @@ -231,7 +231,7 @@ |
| 231 | </actions> | 231 | </actions> |
| 232 | </service> | 232 | </service> |
| 233 | 233 | ||
| 234 | <service verb="mcp" noun="Initialize" authenticate="false" allow-remote="true" transaction-timeout="30"> | 234 | <service verb="mcp" noun="Initialize" authenticate="true" allow-remote="true" transaction-timeout="30"> |
| 235 | <description>Handle MCP initialize request using Moqui authentication</description> | 235 | <description>Handle MCP initialize request using Moqui authentication</description> |
| 236 | <in-parameters> | 236 | <in-parameters> |
| 237 | <parameter name="protocolVersion" required="true"/> | 237 | <parameter name="protocolVersion" required="true"/> |
| ... | @@ -318,14 +318,12 @@ | ... | @@ -318,14 +318,12 @@ |
| 318 | continue | 318 | continue |
| 319 | } | 319 | } |
| 320 | 320 | ||
| 321 | // TODO: Fix permission check - temporarily bypass for testing | 321 | // Check permission using Moqui's artifact authorization |
| 322 | boolean hasPermission = true | 322 | boolean hasPermission = ec.user.hasPermission(serviceName) |
| 323 | ec.logger.info("MCP ToolsList: Service ${serviceName} bypassing permission check for testing") | 323 | ec.logger.info("MCP ToolsList: Service ${serviceName} hasPermission=${hasPermission}") |
| 324 | // boolean hasPermission = ec.user.hasPermission(serviceName) | 324 | if (!hasPermission) { |
| 325 | // ec.logger.info("MCP ToolsList: Service ${serviceName} hasPermission=${hasPermission}") | 325 | continue |
| 326 | // if (!hasPermission) { | 326 | } |
| 327 | // continue | ||
| 328 | // } | ||
| 329 | 327 | ||
| 330 | def serviceDefinition = ec.service.getServiceDefinition(serviceName) | 328 | def serviceDefinition = ec.service.getServiceDefinition(serviceName) |
| 331 | if (!serviceDefinition) continue | 329 | if (!serviceDefinition) continue |
| ... | @@ -363,6 +361,7 @@ | ... | @@ -363,6 +361,7 @@ |
| 363 | } | 361 | } |
| 364 | 362 | ||
| 365 | // Convert Moqui type to JSON Schema type | 363 | // Convert Moqui type to JSON Schema type |
| 364 | // Convert Moqui type to JSON Schema type | ||
| 366 | def typeMap = [ | 365 | def typeMap = [ |
| 367 | "text-short": "string", | 366 | "text-short": "string", |
| 368 | "text-medium": "string", | 367 | "text-medium": "string", |
| ... | @@ -379,14 +378,14 @@ | ... | @@ -379,14 +378,14 @@ |
| 379 | "boolean": "boolean", | 378 | "boolean": "boolean", |
| 380 | "text-indicator": "boolean" | 379 | "text-indicator": "boolean" |
| 381 | ] | 380 | ] |
| 382 | def jsonSchemaType = typeMap[paramInfo.type] ?: "string" | 381 | def jsonSchemaType = typeMap[paramType] ?: "string" |
| 383 | 382 | ||
| 384 | tool.inputSchema.properties[paramName] = [ | 383 | tool.inputSchema.properties[paramName] = [ |
| 385 | type: jsonSchemaType, | 384 | type: jsonSchemaType, |
| 386 | description: paramDesc | 385 | description: paramDesc |
| 387 | ] | 386 | ] |
| 388 | 387 | ||
| 389 | if (paramInfo.required) { | 388 | if (paramNode?.attribute('required') == "true") { |
| 390 | tool.inputSchema.required << paramName | 389 | tool.inputSchema.required << paramName |
| 391 | } | 390 | } |
| 392 | } | 391 | } |
| ... | @@ -815,17 +814,7 @@ | ... | @@ -815,17 +814,7 @@ |
| 815 | </actions> | 814 | </actions> |
| 816 | </service> | 815 | </service> |
| 817 | 816 | ||
| 818 | <service verb="mcp" noun="Ping" authenticate="false" allow-remote="true" transaction-timeout="30"> | 817 | |
| 819 | <description>Simple ping service for MCP testing</description> | ||
| 820 | <out-parameters> | ||
| 821 | <parameter name="message" type="String"/> | ||
| 822 | </out-parameters> | ||
| 823 | <actions> | ||
| 824 | <script><![CDATA[ | ||
| 825 | result = [message: "MCP ping successful at ${new Date()}"] | ||
| 826 | ]]></script> | ||
| 827 | </actions> | ||
| 828 | </service> | ||
| 829 | 818 | ||
| 830 | <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> | 819 | <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> |
| 831 | 820 | ... | ... |
| ... | @@ -14,6 +14,7 @@ | ... | @@ -14,6 +14,7 @@ |
| 14 | package org.moqui.mcp | 14 | package org.moqui.mcp |
| 15 | 15 | ||
| 16 | import groovy.json.JsonSlurper | 16 | import groovy.json.JsonSlurper |
| 17 | import groovy.json.JsonOutput | ||
| 17 | import org.moqui.impl.context.ExecutionContextFactoryImpl | 18 | import org.moqui.impl.context.ExecutionContextFactoryImpl |
| 18 | import org.moqui.context.ArtifactAuthorizationException | 19 | import org.moqui.context.ArtifactAuthorizationException |
| 19 | import org.moqui.context.ArtifactTarpitException | 20 | import org.moqui.context.ArtifactTarpitException |
| ... | @@ -31,6 +32,47 @@ import java.util.concurrent.atomic.AtomicBoolean | ... | @@ -31,6 +32,47 @@ import java.util.concurrent.atomic.AtomicBoolean |
| 31 | import java.util.UUID | 32 | import java.util.UUID |
| 32 | 33 | ||
| 33 | /** | 34 | /** |
| 35 | * Simple JSON-RPC Message classes for MCP compatibility | ||
| 36 | */ | ||
| 37 | class JsonRpcMessage { | ||
| 38 | String jsonrpc = "2.0" | ||
| 39 | } | ||
| 40 | |||
| 41 | class JsonRpcResponse extends JsonRpcMessage { | ||
| 42 | Object id | ||
| 43 | Object result | ||
| 44 | Map error | ||
| 45 | |||
| 46 | JsonRpcResponse(Object result, Object id) { | ||
| 47 | this.result = result | ||
| 48 | this.id = id | ||
| 49 | } | ||
| 50 | |||
| 51 | JsonRpcResponse(Map error, Object id) { | ||
| 52 | this.error = error | ||
| 53 | this.id = id | ||
| 54 | } | ||
| 55 | |||
| 56 | String toJson() { | ||
| 57 | return JsonOutput.toJson(this) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | class JsonRpcNotification extends JsonRpcMessage { | ||
| 62 | String method | ||
| 63 | Object params | ||
| 64 | |||
| 65 | JsonRpcNotification(String method, Object params = null) { | ||
| 66 | this.method = method | ||
| 67 | this.params = params | ||
| 68 | } | ||
| 69 | |||
| 70 | String toJson() { | ||
| 71 | return JsonOutput.toJson(this) | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 34 | * Enhanced MCP Servlet with proper SSE handling inspired by HttpServletSseServerTransportProvider | 76 | * Enhanced MCP Servlet with proper SSE handling inspired by HttpServletSseServerTransportProvider |
| 35 | * This implementation provides better SSE support and session management. | 77 | * This implementation provides better SSE support and session management. |
| 36 | */ | 78 | */ |
| ... | @@ -369,7 +411,7 @@ try { | ... | @@ -369,7 +411,7 @@ try { |
| 369 | def result = processMcpMethod(rpcRequest.method, rpcRequest.params, ec) | 411 | def result = processMcpMethod(rpcRequest.method, rpcRequest.params, ec) |
| 370 | 412 | ||
| 371 | // Send response via MCP transport to the specific session | 413 | // Send response via MCP transport to the specific session |
| 372 | def responseMessage = new McpSchema.JSONRPCMessage(result, rpcRequest.id) | 414 | def responseMessage = new JsonRpcResponse(result, rpcRequest.id) |
| 373 | session.sendMessage(responseMessage) | 415 | session.sendMessage(responseMessage) |
| 374 | 416 | ||
| 375 | response.setContentType("application/json") | 417 | response.setContentType("application/json") |
| ... | @@ -593,7 +635,7 @@ try { | ... | @@ -593,7 +635,7 @@ try { |
| 593 | /** | 635 | /** |
| 594 | * Broadcast message to all active sessions | 636 | * Broadcast message to all active sessions |
| 595 | */ | 637 | */ |
| 596 | void broadcastToAllSessions(McpSchema.JSONRPCMessage message) { | 638 | void broadcastToAllSessions(JsonRpcMessage message) { |
| 597 | sessionManager.broadcast(message) | 639 | sessionManager.broadcast(message) |
| 598 | } | 640 | } |
| 599 | 641 | ... | ... |
| ... | @@ -23,7 +23,7 @@ import javax.servlet.http.HttpServletResponse | ... | @@ -23,7 +23,7 @@ import javax.servlet.http.HttpServletResponse |
| 23 | class McpFilter implements Filter { | 23 | class McpFilter implements Filter { |
| 24 | protected final static Logger logger = LoggerFactory.getLogger(McpFilter.class) | 24 | protected final static Logger logger = LoggerFactory.getLogger(McpFilter.class) |
| 25 | 25 | ||
| 26 | private MoquiMcpServlet mcpServlet = new MoquiMcpServlet() | 26 | private EnhancedMcpServlet mcpServlet = new EnhancedMcpServlet() |
| 27 | 27 | ||
| 28 | @Override | 28 | @Override |
| 29 | void init(FilterConfig filterConfig) throws ServletException { | 29 | void init(FilterConfig filterConfig) throws ServletException { | ... | ... |
| ... | @@ -58,12 +58,11 @@ class McpSessionManager { | ... | @@ -58,12 +58,11 @@ class McpSessionManager { |
| 58 | logger.info("Registered MCP session ${session.sessionId} (total: ${sessions.size()})") | 58 | logger.info("Registered MCP session ${session.sessionId} (total: ${sessions.size()})") |
| 59 | 59 | ||
| 60 | // Send welcome message to new session | 60 | // Send welcome message to new session |
| 61 | def welcomeMessage = new McpSchema.JSONRPCMessage([ | 61 | def welcomeMessage = new JsonRpcNotification("welcome", [ |
| 62 | type: "welcome", | ||
| 63 | sessionId: session.sessionId, | 62 | sessionId: session.sessionId, |
| 64 | totalSessions: sessions.size(), | 63 | totalSessions: sessions.size(), |
| 65 | timestamp: System.currentTimeMillis() | 64 | timestamp: System.currentTimeMillis() |
| 66 | ], null) | 65 | ]) |
| 67 | session.sendMessage(welcomeMessage) | 66 | session.sendMessage(welcomeMessage) |
| 68 | } | 67 | } |
| 69 | 68 | ||
| ... | @@ -87,7 +86,7 @@ class McpSessionManager { | ... | @@ -87,7 +86,7 @@ class McpSessionManager { |
| 87 | /** | 86 | /** |
| 88 | * Broadcast message to all active sessions | 87 | * Broadcast message to all active sessions |
| 89 | */ | 88 | */ |
| 90 | void broadcast(McpSchema.JSONRPCMessage message) { | 89 | void broadcast(JsonRpcMessage message) { |
| 91 | if (isShuttingDown.get()) { | 90 | if (isShuttingDown.get()) { |
| 92 | logger.warn("Rejecting broadcast during shutdown") | 91 | logger.warn("Rejecting broadcast during shutdown") |
| 93 | return | 92 | return |
| ... | @@ -121,7 +120,7 @@ class McpSessionManager { | ... | @@ -121,7 +120,7 @@ class McpSessionManager { |
| 121 | /** | 120 | /** |
| 122 | * Send message to specific session | 121 | * Send message to specific session |
| 123 | */ | 122 | */ |
| 124 | boolean sendToSession(String sessionId, McpSchema.JSONRPCMessage message) { | 123 | boolean sendToSession(String sessionId, JsonRpcMessage message) { |
| 125 | def session = sessions.get(sessionId) | 124 | def session = sessions.get(sessionId) |
| 126 | if (!session) { | 125 | if (!session) { |
| 127 | return false | 126 | return false |
| ... | @@ -181,11 +180,10 @@ class McpSessionManager { | ... | @@ -181,11 +180,10 @@ class McpSessionManager { |
| 181 | logger.info("Initiating graceful MCP session manager shutdown") | 180 | logger.info("Initiating graceful MCP session manager shutdown") |
| 182 | 181 | ||
| 183 | // Send shutdown notification to all sessions | 182 | // Send shutdown notification to all sessions |
| 184 | def shutdownMessage = new McpSchema.JSONRPCMessage([ | 183 | def shutdownMessage = new JsonRpcNotification("server_shutdown", [ |
| 185 | type: "server_shutdown", | ||
| 186 | message: "Server is shutting down gracefully", | 184 | message: "Server is shutting down gracefully", |
| 187 | timestamp: System.currentTimeMillis() | 185 | timestamp: System.currentTimeMillis() |
| 188 | ], null) | 186 | ]) |
| 189 | broadcast(shutdownMessage) | 187 | broadcast(shutdownMessage) |
| 190 | 188 | ||
| 191 | // Give sessions time to receive shutdown message | 189 | // Give sessions time to receive shutdown message | ... | ... |
This diff is collapsed.
Click to expand it.
| ... | @@ -13,93 +13,11 @@ | ... | @@ -13,93 +13,11 @@ |
| 13 | */ | 13 | */ |
| 14 | package org.moqui.mcp | 14 | package org.moqui.mcp |
| 15 | 15 | ||
| 16 | import groovy.json.JsonBuilder | ||
| 17 | |||
| 18 | /** | 16 | /** |
| 19 | * MCP Transport interface compatible with Servlet 4.0 and Moqui Visit system | 17 | * Simple transport interface for MCP messages |
| 20 | * Provides SDK-style session management capabilities while maintaining compatibility | ||
| 21 | */ | 18 | */ |
| 22 | interface MoquiMcpTransport { | 19 | interface MoquiMcpTransport { |
| 23 | /** | 20 | void sendMessage(JsonRpcMessage message) |
| 24 | * Send a JSON-RPC message through this transport | ||
| 25 | * @param message The MCP JSON-RPC message to send | ||
| 26 | */ | ||
| 27 | void sendMessage(McpSchema.JSONRPCMessage message) | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Close the transport gracefully, allowing in-flight messages to complete | ||
| 31 | */ | ||
| 32 | void closeGracefully() | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Force close the transport immediately | ||
| 36 | */ | ||
| 37 | void close() | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Check if the transport is still active | ||
| 41 | * @return true if transport is active, false otherwise | ||
| 42 | */ | ||
| 43 | boolean isActive() | 21 | boolean isActive() |
| 44 | |||
| 45 | /** | ||
| 46 | * Get the session ID associated with this transport | ||
| 47 | * @return the MCP session ID | ||
| 48 | */ | ||
| 49 | String getSessionId() | 22 | String getSessionId() |
| 50 | |||
| 51 | /** | ||
| 52 | * Get the associated Moqui Visit ID | ||
| 53 | * @return the Visit ID if available, null otherwise | ||
| 54 | */ | ||
| 55 | String getVisitId() | ||
| 56 | } | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Simple implementation of MCP JSON-RPC message schema | ||
| 60 | * Compatible with MCP protocol specifications | ||
| 61 | */ | ||
| 62 | class McpSchema { | ||
| 63 | static class JSONRPCMessage { | ||
| 64 | String jsonrpc = "2.0" | ||
| 65 | Object id | ||
| 66 | String method | ||
| 67 | Map params | ||
| 68 | Object result | ||
| 69 | Map error | ||
| 70 | |||
| 71 | JSONRPCMessage(String method, Map params = null, Object id = null) { | ||
| 72 | this.method = method | ||
| 73 | this.params = params | ||
| 74 | this.id = id | ||
| 75 | } | ||
| 76 | |||
| 77 | JSONRPCMessage(Object result, Object id) { | ||
| 78 | this.result = result | ||
| 79 | this.id = id | ||
| 80 | } | ||
| 81 | |||
| 82 | JSONRPCMessage(Map error, Object id) { | ||
| 83 | this.error = error | ||
| 84 | this.id = id | ||
| 85 | } | ||
| 86 | |||
| 87 | String toJson() { | ||
| 88 | return new JsonBuilder(this).toString() | ||
| 89 | } | ||
| 90 | |||
| 91 | static JSONRPCMessage fromJson(String json) { | ||
| 92 | // Simple JSON parsing - in production would use proper JSON parser | ||
| 93 | def slurper = new groovy.json.JsonSlurper() | ||
| 94 | def data = slurper.parseText(json) | ||
| 95 | |||
| 96 | if (data.error) { | ||
| 97 | return new JSONRPCMessage(data.error, data.id) | ||
| 98 | } else if (data.result != null) { | ||
| 99 | return new JSONRPCMessage(data.result, data.id) | ||
| 100 | } else { | ||
| 101 | return new JSONRPCMessage(data.method, data.params, data.id) | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | 23 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -258,15 +258,18 @@ class ServiceBasedMcpServlet extends HttpServlet { | ... | @@ -258,15 +258,18 @@ class ServiceBasedMcpServlet extends HttpServlet { |
| 258 | 258 | ||
| 259 | logger.info("Service-Based MCP Message authenticated user: ${ec.user?.username}, userId: ${ec.user?.userId}") | 259 | logger.info("Service-Based MCP Message authenticated user: ${ec.user?.username}, userId: ${ec.user?.userId}") |
| 260 | 260 | ||
| 261 | // If no user authenticated, try to authenticate as admin for MCP requests | 261 | // Require authentication - do not fallback to admin |
| 262 | if (!ec.user?.userId) { | 262 | if (!ec.user?.userId) { |
| 263 | logger.info("No user authenticated, attempting admin login for Service-Based MCP") | 263 | logger.warn("Service-Based MCP Request denied - no authenticated user") |
| 264 | try { | 264 | // Handle error directly without sendError to avoid Moqui error screen interference |
| 265 | ec.user.loginUser("admin", "admin") | 265 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) |
| 266 | logger.info("Service-Based MCP Admin login successful, user: ${ec.user?.username}") | 266 | response.setContentType("application/json") |
| 267 | } catch (Exception e) { | 267 | response.writer.write(groovy.json.JsonOutput.toJson([ |
| 268 | logger.warn("Service-Based MCP Admin login failed: ${e.message}") | 268 | jsonrpc: "2.0", |
| 269 | } | 269 | error: [code: -32000, message: "Authentication required. Please provide valid credentials."], |
| 270 | id: null | ||
| 271 | ])) | ||
| 272 | return | ||
| 270 | } | 273 | } |
| 271 | 274 | ||
| 272 | // Handle different HTTP methods | 275 | // Handle different HTTP methods |
| ... | @@ -435,15 +438,18 @@ class ServiceBasedMcpServlet extends HttpServlet { | ... | @@ -435,15 +438,18 @@ class ServiceBasedMcpServlet extends HttpServlet { |
| 435 | // Initialize web facade for authentication | 438 | // Initialize web facade for authentication |
| 436 | ec.initWebFacade(webappName, request, response) | 439 | ec.initWebFacade(webappName, request, response) |
| 437 | 440 | ||
| 438 | // If no user authenticated, try to authenticate as admin for MCP requests | 441 | // Require authentication - do not fallback to admin |
| 439 | if (!ec.user?.userId) { | 442 | if (!ec.user?.userId) { |
| 440 | logger.info("No user authenticated, attempting admin login for Legacy MCP") | 443 | logger.warn("Legacy MCP Request denied - no authenticated user") |
| 441 | try { | 444 | // Handle error directly without sendError to avoid Moqui error screen interference |
| 442 | ec.user.loginUser("admin", "admin") | 445 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) |
| 443 | logger.info("Legacy MCP Admin login successful, user: ${ec.user?.username}") | 446 | response.setContentType("application/json") |
| 444 | } catch (Exception e) { | 447 | response.writer.write(groovy.json.JsonOutput.toJson([ |
| 445 | logger.warn("Legacy MCP Admin login failed: ${e.message}") | 448 | jsonrpc: "2.0", |
| 446 | } | 449 | error: [code: -32000, message: "Authentication required. Please provide valid credentials."], |
| 450 | id: null | ||
| 451 | ])) | ||
| 452 | return | ||
| 447 | } | 453 | } |
| 448 | 454 | ||
| 449 | // Read and parse JSON-RPC request (same as POST handling) | 455 | // Read and parse JSON-RPC request (same as POST handling) | ... | ... |
| ... | @@ -73,7 +73,7 @@ class VisitBasedMcpSession implements MoquiMcpTransport { | ... | @@ -73,7 +73,7 @@ class VisitBasedMcpSession implements MoquiMcpTransport { |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | @Override | 75 | @Override |
| 76 | void sendMessage(McpSchema.JSONRPCMessage message) { | 76 | void sendMessage(JsonRpcMessage message) { |
| 77 | if (!active.get() || closing.get()) { | 77 | if (!active.get() || closing.get()) { |
| 78 | logger.warn("Attempted to send message on inactive or closing session ${sessionId}") | 78 | logger.warn("Attempted to send message on inactive or closing session ${sessionId}") |
| 79 | return | 79 | return |
| ... | @@ -95,7 +95,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { | ... | @@ -95,7 +95,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { |
| 95 | } | 95 | } |
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | @Override | ||
| 99 | void closeGracefully() { | 98 | void closeGracefully() { |
| 100 | if (!active.compareAndSet(true, false)) { | 99 | if (!active.compareAndSet(true, false)) { |
| 101 | return // Already closed | 100 | return // Already closed |
| ... | @@ -106,11 +105,10 @@ class VisitBasedMcpSession implements MoquiMcpTransport { | ... | @@ -106,11 +105,10 @@ class VisitBasedMcpSession implements MoquiMcpTransport { |
| 106 | 105 | ||
| 107 | try { | 106 | try { |
| 108 | // Send graceful shutdown notification | 107 | // Send graceful shutdown notification |
| 109 | def shutdownMessage = new McpSchema.JSONRPCMessage([ | 108 | def shutdownMessage = new JsonRpcNotification("shutdown", [ |
| 110 | type: "shutdown", | ||
| 111 | sessionId: sessionId, | 109 | sessionId: sessionId, |
| 112 | timestamp: System.currentTimeMillis() | 110 | timestamp: System.currentTimeMillis() |
| 113 | ], null) | 111 | ]) |
| 114 | sendMessage(shutdownMessage) | 112 | sendMessage(shutdownMessage) |
| 115 | 113 | ||
| 116 | // Give some time for message to be sent | 114 | // Give some time for message to be sent |
| ... | @@ -123,7 +121,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { | ... | @@ -123,7 +121,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { |
| 123 | } | 121 | } |
| 124 | } | 122 | } |
| 125 | 123 | ||
| 126 | @Override | ||
| 127 | void close() { | 124 | void close() { |
| 128 | if (!active.compareAndSet(true, false)) { | 125 | if (!active.compareAndSet(true, false)) { |
| 129 | return // Already closed | 126 | return // Already closed |
| ... | @@ -160,7 +157,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { | ... | @@ -160,7 +157,6 @@ class VisitBasedMcpSession implements MoquiMcpTransport { |
| 160 | return sessionId | 157 | return sessionId |
| 161 | } | 158 | } |
| 162 | 159 | ||
| 163 | @Override | ||
| 164 | String getVisitId() { | 160 | String getVisitId() { |
| 165 | return visitId | 161 | return visitId |
| 166 | } | 162 | } | ... | ... |
| ... | @@ -21,18 +21,9 @@ | ... | @@ -21,18 +21,9 @@ |
| 21 | 21 | ||
| 22 | <!-- Service-Based MCP Servlet Configuration --> | 22 | <!-- Service-Based MCP Servlet Configuration --> |
| 23 | <servlet> | 23 | <servlet> |
| 24 | <servlet-name>ServiceBasedMcpServlet</servlet-name> | 24 | <servlet-name>EnhancedMcpServlet</servlet-name> |
| 25 | <servlet-class>org.moqui.mcp.ServiceBasedMcpServlet</servlet-class> | 25 | <servlet-class>org.moqui.mcp.EnhancedMcpServlet</servlet-class> |
| 26 | 26 | ||
| 27 | <!-- Configuration Parameters --> | ||
| 28 | <init-param> | ||
| 29 | <param-name>sseEndpoint</param-name> | ||
| 30 | <param-value>/sse</param-value> | ||
| 31 | </init-param> | ||
| 32 | <init-param> | ||
| 33 | <param-name>messageEndpoint</param-name> | ||
| 34 | <param-value>/mcp/message</param-value> | ||
| 35 | </init-param> | ||
| 36 | <init-param> | 27 | <init-param> |
| 37 | <param-name>keepAliveIntervalSeconds</param-name> | 28 | <param-name>keepAliveIntervalSeconds</param-name> |
| 38 | <param-value>30</param-value> | 29 | <param-value>30</param-value> |
| ... | @@ -49,20 +40,9 @@ | ... | @@ -49,20 +40,9 @@ |
| 49 | <load-on-startup>5</load-on-startup> | 40 | <load-on-startup>5</load-on-startup> |
| 50 | </servlet> | 41 | </servlet> |
| 51 | 42 | ||
| 52 | <!-- Servlet Mappings --> | ||
| 53 | <servlet-mapping> | 43 | <servlet-mapping> |
| 54 | <servlet-name>ServiceBasedMcpServlet</servlet-name> | 44 | <servlet-name>EnhancedMcpServlet</servlet-name> |
| 55 | <url-pattern>/sse/*</url-pattern> | 45 | <url-pattern>/mcp/*</url-pattern> |
| 56 | </servlet-mapping> | ||
| 57 | |||
| 58 | <servlet-mapping> | ||
| 59 | <servlet-name>ServiceBasedMcpServlet</servlet-name> | ||
| 60 | <url-pattern>/mcp/message/*</url-pattern> | ||
| 61 | </servlet-mapping> | ||
| 62 | |||
| 63 | <servlet-mapping> | ||
| 64 | <servlet-name>ServiceBasedMcpServlet</servlet-name> | ||
| 65 | <url-pattern>/rpc/*</url-pattern> | ||
| 66 | </servlet-mapping> | 46 | </servlet-mapping> |
| 67 | 47 | ||
| 68 | <!-- Session Configuration --> | 48 | <!-- Session Configuration --> | ... | ... |
web-sse.xml
deleted
100644 → 0
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" | ||
| 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee | ||
| 5 | http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" | ||
| 6 | version="4.0"> | ||
| 7 | |||
| 8 | <!-- MCP SSE Servlet Configuration --> | ||
| 9 | <servlet> | ||
| 10 | <servlet-name>EnhancedMcpServlet</servlet-name> | ||
| 11 | <servlet-class>org.moqui.mcp.EnhancedMcpServlet</servlet-class> | ||
| 12 | |||
| 13 | <!-- Configuration parameters --> | ||
| 14 | <init-param> | ||
| 15 | <param-name>moqui-name</param-name> | ||
| 16 | <param-value>moqui-mcp-2</param-value> | ||
| 17 | </init-param> | ||
| 18 | |||
| 19 | <init-param> | ||
| 20 | <param-name>sseEndpoint</param-name> | ||
| 21 | <param-value>/sse</param-value> | ||
| 22 | </init-param> | ||
| 23 | |||
| 24 | <init-param> | ||
| 25 | <param-name>messageEndpoint</param-name> | ||
| 26 | <param-value>/mcp/message</param-value> | ||
| 27 | </init-param> | ||
| 28 | |||
| 29 | <init-param> | ||
| 30 | <param-name>keepAliveIntervalSeconds</param-name> | ||
| 31 | <param-value>30</param-value> | ||
| 32 | </init-param> | ||
| 33 | |||
| 34 | <init-param> | ||
| 35 | <param-name>maxConnections</param-name> | ||
| 36 | <param-value>100</param-value> | ||
| 37 | </init-param> | ||
| 38 | |||
| 39 | <!-- Enable async support --> | ||
| 40 | <async-supported>true</async-supported> | ||
| 41 | </servlet> | ||
| 42 | |||
| 43 | <!-- Servlet mappings for MCP SSE endpoints --> | ||
| 44 | <servlet-mapping> | ||
| 45 | <servlet-name>EnhancedMcpServlet</servlet-name> | ||
| 46 | <url-pattern>/sse/*</url-pattern> | ||
| 47 | </servlet-mapping> | ||
| 48 | |||
| 49 | <servlet-mapping> | ||
| 50 | <servlet-name>EnhancedMcpServlet</servlet-name> | ||
| 51 | <url-pattern>/mcp/message/*</url-pattern> | ||
| 52 | </servlet-mapping> | ||
| 53 | |||
| 54 | <!-- Session configuration --> | ||
| 55 | <session-config> | ||
| 56 | <session-timeout>30</session-timeout> | ||
| 57 | <cookie-config> | ||
| 58 | <http-only>true</http-only> | ||
| 59 | <secure>false</secure> | ||
| 60 | </cookie-config> | ||
| 61 | </session-config> | ||
| 62 | |||
| 63 | </web-app> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or sign in to post a comment