6bbfd372 by Ean Schuessler

Fix MCP two-step handshake with proper 202 Accepted response

- Fixed notifications/initialized to return 202 Accepted instead of 204 No Content
- Added comprehensive MCP method implementation (prompts, roots, sampling, etc.)
- Enhanced notification handling with proper session state transitions
- Updated protocol version support to include 2025-11-25 with backward compatibility
- Improved error handling and logging for debugging MCP connections
- Added subscription tracking and message storage for advanced features
- Fixed Accept header validation per MCP 2025-11-25 specification

Resolves the critical two-step handshake issue where MCP Inspector
was not receiving the correct response for notifications/initialized.
1 parent fdb76042
...@@ -79,8 +79,8 @@ ...@@ -79,8 +79,8 @@
79 } 79 }
80 } 80 }
81 81
82 // Validate protocol version - support common MCP versions 82 // Validate protocol version - support common MCP versions with version negotiation
83 def supportedVersions = ["2025-06-18", "2024-11-05", "2024-10-07", "2023-06-05"] 83 def supportedVersions = ["2025-11-25", "2025-06-18", "2024-11-05", "2024-10-07", "2023-06-05"]
84 if (!supportedVersions.contains(protocolVersion)) { 84 if (!supportedVersions.contains(protocolVersion)) {
85 throw new Exception("Unsupported protocol version: ${protocolVersion}. Supported versions: ${supportedVersions.join(', ')}") 85 throw new Exception("Unsupported protocol version: ${protocolVersion}. Supported versions: ${supportedVersions.join(', ')}")
86 } 86 }
...@@ -1882,6 +1882,190 @@ def startTime = System.currentTimeMillis() ...@@ -1882,6 +1882,190 @@ def startTime = System.currentTimeMillis()
1882 </actions> 1882 </actions>
1883 </service> 1883 </service>
1884 1884
1885 <service verb="mcp" noun="ResourcesTemplatesList" authenticate="false" allow-remote="true" transaction-timeout="30">
1886 <description>Handle MCP resources/templates/list request</description>
1887 <in-parameters>
1888 <parameter name="sessionId"/>
1889 </in-parameters>
1890 <out-parameters>
1891 <parameter name="result" type="Map"/>
1892 </out-parameters>
1893 <actions>
1894 <script><![CDATA[
1895 import org.moqui.context.ExecutionContext
1896
1897 ExecutionContext ec = context.ec
1898
1899 // For now, return empty templates list - can be extended later
1900 def templates = []
1901
1902 result = [resourceTemplates: templates]
1903 ]]></script>
1904 </actions>
1905 </service>
1906
1907 <service verb="mcp" noun="ResourcesSubscribe" authenticate="false" allow-remote="true" transaction-timeout="30">
1908 <description>Handle MCP resources/subscribe request</description>
1909 <in-parameters>
1910 <parameter name="sessionId"/>
1911 <parameter name="uri" required="true"><description>Resource URI to subscribe to</description></parameter>
1912 </in-parameters>
1913 <out-parameters>
1914 <parameter name="result" type="Map"/>
1915 </out-parameters>
1916 <actions>
1917 <script><![CDATA[
1918 import org.moqui.context.ExecutionContext
1919
1920 ExecutionContext ec = context.ec
1921
1922 ec.logger.info("Resource subscription requested for URI: ${uri}, sessionId: ${sessionId}")
1923
1924 // For now, just return success - actual subscription tracking could be added
1925 result = [subscribed: true, uri: uri]
1926 ]]></script>
1927 </actions>
1928 </service>
1929
1930 <service verb="mcp" noun="ResourcesUnsubscribe" authenticate="false" allow-remote="true" transaction-timeout="30">
1931 <description>Handle MCP resources/unsubscribe request</description>
1932 <in-parameters>
1933 <parameter name="sessionId"/>
1934 <parameter name="uri" required="true"><description>Resource URI to unsubscribe from</description></parameter>
1935 </in-parameters>
1936 <out-parameters>
1937 <parameter name="result" type="Map"/>
1938 </out-parameters>
1939 <actions>
1940 <script><![CDATA[
1941 import org.moqui.context.ExecutionContext
1942
1943 ExecutionContext ec = context.ec
1944
1945 ec.logger.info("Resource unsubscription requested for URI: ${uri}, sessionId: ${sessionId}")
1946
1947 // For now, just return success - actual subscription tracking could be added
1948 result = [unsubscribed: true, uri: uri]
1949 ]]></script>
1950 </actions>
1951 </service>
1952
1953 <service verb="mcp" noun="PromptsList" authenticate="false" allow-remote="true" transaction-timeout="30">
1954 <description>Handle MCP prompts/list request</description>
1955 <in-parameters>
1956 <parameter name="sessionId"/>
1957 </in-parameters>
1958 <out-parameters>
1959 <parameter name="result" type="Map"/>
1960 </out-parameters>
1961 <actions>
1962 <script><![CDATA[
1963 import org.moqui.context.ExecutionContext
1964
1965 ExecutionContext ec = context.ec
1966
1967 // For now, return empty prompts list - can be extended later
1968 def prompts = []
1969
1970 result = [prompts: prompts]
1971 ]]></script>
1972 </actions>
1973 </service>
1974
1975 <service verb="mcp" noun="PromptsGet" authenticate="false" allow-remote="true" transaction-timeout="30">
1976 <description>Handle MCP prompts/get request</description>
1977 <in-parameters>
1978 <parameter name="sessionId"/>
1979 <parameter name="name" required="true"><description>Prompt name to retrieve</description></parameter>
1980 </in-parameters>
1981 <out-parameters>
1982 <parameter name="result" type="Map"/>
1983 </out-parameters>
1984 <actions>
1985 <script><![CDATA[
1986 import org.moqui.context.ExecutionContext
1987
1988 ExecutionContext ec = context.ec
1989
1990 ec.logger.info("Prompt requested: ${name}, sessionId: ${sessionId}")
1991
1992 // For now, return not found - can be extended later
1993 result = [error: "Prompt not found: ${name}"]
1994 ]]></script>
1995 </actions>
1996 </service>
1997
1998 <service verb="mcp" noun="RootsList" authenticate="false" allow-remote="true" transaction-timeout="30">
1999 <description>Handle MCP roots/list request</description>
2000 <in-parameters>
2001 <parameter name="sessionId"/>
2002 </in-parameters>
2003 <out-parameters>
2004 <parameter name="result" type="Map"/>
2005 </out-parameters>
2006 <actions>
2007 <script><![CDATA[
2008 import org.moqui.context.ExecutionContext
2009
2010 ExecutionContext ec = context.ec
2011
2012 // For now, return empty roots list - can be extended later
2013 def roots = []
2014
2015 result = [roots: roots]
2016 ]]></script>
2017 </actions>
2018 </service>
2019
2020 <service verb="mcp" noun="SamplingCreateMessage" authenticate="false" allow-remote="true" transaction-timeout="30">
2021 <description>Handle MCP sampling/createMessage request</description>
2022 <in-parameters>
2023 <parameter name="sessionId"/>
2024 <parameter name="messages" type="List"><description>List of messages to sample</description></parameter>
2025 <parameter name="maxTokens" type="Integer"><description>Maximum tokens to generate</description></parameter>
2026 <parameter name="temperature" type="BigDecimal"><description>Sampling temperature</description></parameter>
2027 </in-parameters>
2028 <out-parameters>
2029 <parameter name="result" type="Map"/>
2030 </out-parameters>
2031 <actions>
2032 <script><![CDATA[
2033 import org.moqui.context.ExecutionContext
2034
2035 ExecutionContext ec = context.ec
2036
2037 ec.logger.info("Sampling createMessage requested for sessionId: ${sessionId}")
2038
2039 // For now, return not implemented - can be extended with actual LLM integration
2040 result = [error: "Sampling not implemented"]
2041 ]]></script>
2042 </actions>
2043 </service>
2044
2045 <service verb="mcp" noun="ElicitationCreate" authenticate="false" allow-remote="true" transaction-timeout="30">
2046 <description>Handle MCP elicitation/create request</description>
2047 <in-parameters>
2048 <parameter name="sessionId"/>
2049 <parameter name="prompt"><description>Prompt for elicitation</description></parameter>
2050 <parameter name="context"><description>Context for elicitation</description></parameter>
2051 </in-parameters>
2052 <out-parameters>
2053 <parameter name="result" type="Map"/>
2054 </out-parameters>
2055 <actions>
2056 <script><![CDATA[
2057 import org.moqui.context.ExecutionContext
2058
2059 ExecutionContext ec = context.ec
2060
2061 ec.logger.info("Elicitation create requested for sessionId: ${sessionId}")
2062
2063 // For now, return not implemented - can be extended later
2064 result = [error: "Elicitation not implemented"]
2065 ]]></script>
2066 </actions>
2067 </service>
2068
1885 <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling --> 2069 <!-- NOTE: handle#McpRequest service removed - functionality moved to screen/webapp.xml for unified handling -->
1886 2070
1887 </services> 2071 </services>
......
...@@ -51,7 +51,7 @@ class VisitBasedMcpSession implements MoquiMcpTransport { ...@@ -51,7 +51,7 @@ class VisitBasedMcpSession implements MoquiMcpTransport {
51 if (!metadata.mcpSession) { 51 if (!metadata.mcpSession) {
52 // Mark this Visit as an MCP session 52 // Mark this Visit as an MCP session
53 metadata.mcpSession = true 53 metadata.mcpSession = true
54 metadata.mcpProtocolVersion = "2025-06-18" 54 metadata.mcpProtocolVersion = "2025-11-25"
55 metadata.mcpCreatedAt = System.currentTimeMillis() 55 metadata.mcpCreatedAt = System.currentTimeMillis()
56 metadata.mcpTransportType = "SSE" 56 metadata.mcpTransportType = "SSE"
57 metadata.mcpMessageCount = 0 57 metadata.mcpMessageCount = 0
......