@@ -1802,6 +1802,190 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
18021802 // Clean up - resolve the tool promise
18031803 toolResolve ! ( ) ;
18041804 } ) ;
1805+
1806+ it ( 'should provide closeSSEStream callback in extra when eventStore is configured' , async ( ) => {
1807+ const result = await createTestServer ( {
1808+ sessionIdGenerator : ( ) => randomUUID ( ) ,
1809+ eventStore : createEventStore ( ) ,
1810+ retryInterval : 1000
1811+ } ) ;
1812+ server = result . server ;
1813+ transport = result . transport ;
1814+ baseUrl = result . baseUrl ;
1815+ mcpServer = result . mcpServer ;
1816+
1817+ // Track whether closeSSEStream callback was provided
1818+ let receivedCloseSSEStream : ( ( options ?: { retryInterval ?: number } ) => void ) | undefined ;
1819+
1820+ // Register a tool that captures the extra.closeSSEStream callback
1821+ mcpServer . tool ( 'test-callback-tool' , 'Test tool' , { } , async ( _args , extra ) => {
1822+ receivedCloseSSEStream = extra . closeSSEStream ;
1823+ return { content : [ { type : 'text' , text : 'Done' } ] } ;
1824+ } ) ;
1825+
1826+ // Initialize to get session ID
1827+ const initResponse = await sendPostRequest ( baseUrl , TEST_MESSAGES . initialize ) ;
1828+ sessionId = initResponse . headers . get ( 'mcp-session-id' ) as string ;
1829+ expect ( sessionId ) . toBeDefined ( ) ;
1830+
1831+ // Call the tool
1832+ const toolCallRequest : JSONRPCMessage = {
1833+ jsonrpc : '2.0' ,
1834+ id : 200 ,
1835+ method : 'tools/call' ,
1836+ params : { name : 'test-callback-tool' , arguments : { } }
1837+ } ;
1838+
1839+ const postResponse = await fetch ( baseUrl , {
1840+ method : 'POST' ,
1841+ headers : {
1842+ 'Content-Type' : 'application/json' ,
1843+ Accept : 'text/event-stream, application/json' ,
1844+ 'mcp-session-id' : sessionId ,
1845+ 'mcp-protocol-version' : '2025-03-26'
1846+ } ,
1847+ body : JSON . stringify ( toolCallRequest )
1848+ } ) ;
1849+
1850+ expect ( postResponse . status ) . toBe ( 200 ) ;
1851+
1852+ // Read all events to completion
1853+ const reader = postResponse . body ?. getReader ( ) ;
1854+ while ( true ) {
1855+ const { done } = await reader ! . read ( ) ;
1856+ if ( done ) break ;
1857+ }
1858+
1859+ // Verify closeSSEStream callback was provided
1860+ expect ( receivedCloseSSEStream ) . toBeDefined ( ) ;
1861+ expect ( typeof receivedCloseSSEStream ) . toBe ( 'function' ) ;
1862+ } ) ;
1863+
1864+ it ( 'should NOT provide closeSSEStream callback when eventStore is NOT configured' , async ( ) => {
1865+ const result = await createTestServer ( {
1866+ sessionIdGenerator : ( ) => randomUUID ( )
1867+ // No eventStore
1868+ } ) ;
1869+ server = result . server ;
1870+ transport = result . transport ;
1871+ baseUrl = result . baseUrl ;
1872+ mcpServer = result . mcpServer ;
1873+
1874+ // Track whether closeSSEStream callback was provided
1875+ let receivedCloseSSEStream : ( ( options ?: { retryInterval ?: number } ) => void ) | undefined ;
1876+
1877+ // Register a tool that captures the extra.closeSSEStream callback
1878+ mcpServer . tool ( 'test-no-callback-tool' , 'Test tool' , { } , async ( _args , extra ) => {
1879+ receivedCloseSSEStream = extra . closeSSEStream ;
1880+ return { content : [ { type : 'text' , text : 'Done' } ] } ;
1881+ } ) ;
1882+
1883+ // Initialize to get session ID
1884+ const initResponse = await sendPostRequest ( baseUrl , TEST_MESSAGES . initialize ) ;
1885+ sessionId = initResponse . headers . get ( 'mcp-session-id' ) as string ;
1886+ expect ( sessionId ) . toBeDefined ( ) ;
1887+
1888+ // Call the tool
1889+ const toolCallRequest : JSONRPCMessage = {
1890+ jsonrpc : '2.0' ,
1891+ id : 201 ,
1892+ method : 'tools/call' ,
1893+ params : { name : 'test-no-callback-tool' , arguments : { } }
1894+ } ;
1895+
1896+ const postResponse = await fetch ( baseUrl , {
1897+ method : 'POST' ,
1898+ headers : {
1899+ 'Content-Type' : 'application/json' ,
1900+ Accept : 'text/event-stream, application/json' ,
1901+ 'mcp-session-id' : sessionId ,
1902+ 'mcp-protocol-version' : '2025-03-26'
1903+ } ,
1904+ body : JSON . stringify ( toolCallRequest )
1905+ } ) ;
1906+
1907+ expect ( postResponse . status ) . toBe ( 200 ) ;
1908+
1909+ // Read all events to completion
1910+ const reader = postResponse . body ?. getReader ( ) ;
1911+ while ( true ) {
1912+ const { done } = await reader ! . read ( ) ;
1913+ if ( done ) break ;
1914+ }
1915+
1916+ // Verify closeSSEStream callback was NOT provided
1917+ expect ( receivedCloseSSEStream ) . toBeUndefined ( ) ;
1918+ } ) ;
1919+
1920+ it ( 'should send custom retry interval when closeSSEStream is called with retryInterval' , async ( ) => {
1921+ const result = await createTestServer ( {
1922+ sessionIdGenerator : ( ) => randomUUID ( ) ,
1923+ eventStore : createEventStore ( ) ,
1924+ retryInterval : 1000 // Default
1925+ } ) ;
1926+ server = result . server ;
1927+ transport = result . transport ;
1928+ baseUrl = result . baseUrl ;
1929+ mcpServer = result . mcpServer ;
1930+
1931+ // Track tool execution state
1932+ let toolResolve : ( ) => void ;
1933+ const toolPromise = new Promise < void > ( resolve => {
1934+ toolResolve = resolve ;
1935+ } ) ;
1936+
1937+ // Register a tool that uses closeSSEStream with custom retry interval
1938+ mcpServer . tool ( 'custom-retry-tool' , 'Test tool' , { } , async ( _args , extra ) => {
1939+ // Use closeSSEStream with custom retry interval
1940+ extra . closeSSEStream ?.( { retryInterval : 5000 } ) ;
1941+ await toolPromise ;
1942+ return { content : [ { type : 'text' , text : 'Done' } ] } ;
1943+ } ) ;
1944+
1945+ // Initialize to get session ID
1946+ const initResponse = await sendPostRequest ( baseUrl , TEST_MESSAGES . initialize ) ;
1947+ sessionId = initResponse . headers . get ( 'mcp-session-id' ) as string ;
1948+ expect ( sessionId ) . toBeDefined ( ) ;
1949+
1950+ // Call the tool
1951+ const toolCallRequest : JSONRPCMessage = {
1952+ jsonrpc : '2.0' ,
1953+ id : 202 ,
1954+ method : 'tools/call' ,
1955+ params : { name : 'custom-retry-tool' , arguments : { } }
1956+ } ;
1957+
1958+ const postResponse = await fetch ( baseUrl , {
1959+ method : 'POST' ,
1960+ headers : {
1961+ 'Content-Type' : 'application/json' ,
1962+ Accept : 'text/event-stream, application/json' ,
1963+ 'mcp-session-id' : sessionId ,
1964+ 'mcp-protocol-version' : '2025-03-26'
1965+ } ,
1966+ body : JSON . stringify ( toolCallRequest )
1967+ } ) ;
1968+
1969+ expect ( postResponse . status ) . toBe ( 200 ) ;
1970+
1971+ // Collect all SSE data
1972+ const reader = postResponse . body ?. getReader ( ) ;
1973+ let allText = '' ;
1974+ while ( true ) {
1975+ const { done, value } = await reader ! . read ( ) ;
1976+ if ( value ) {
1977+ allText += new TextDecoder ( ) . decode ( value ) ;
1978+ }
1979+ if ( done ) break ;
1980+ }
1981+
1982+ // Verify the custom retry interval was sent
1983+ // The stream should contain "retry: 5000" (the custom value)
1984+ expect ( allText ) . toContain ( 'retry: 5000' ) ;
1985+
1986+ // Clean up
1987+ toolResolve ! ( ) ;
1988+ } ) ;
18051989 } ) ;
18061990
18071991 // Test onsessionclosed callback
0 commit comments