@@ -1771,6 +1771,107 @@ describe('StreamableHTTPServerTransport POST SSE priming events', () => {
17711771 // Clean up - resolve the tool promise
17721772 toolResolve ! ( ) ;
17731773 } ) ;
1774+
1775+ it ( 'should support POST SSE polling with client reconnection' , async ( ) => {
1776+ const result = await createTestServer ( {
1777+ sessionIdGenerator : ( ) => randomUUID ( ) ,
1778+ eventStore : createEventStore ( ) ,
1779+ retryInterval : 1000
1780+ } ) ;
1781+ server = result . server ;
1782+ transport = result . transport ;
1783+ baseUrl = result . baseUrl ;
1784+ mcpServer = result . mcpServer ;
1785+
1786+ // Track tool execution state
1787+ let toolResolve : ( ) => void ;
1788+ const toolPromise = new Promise < void > ( resolve => {
1789+ toolResolve = resolve ;
1790+ } ) ;
1791+
1792+ // Register a tool that sends progress and then completes
1793+ mcpServer . tool ( 'polling-tool' , 'A tool for polling test' , { } , async ( ) => {
1794+ // Wait for test to signal completion
1795+ await toolPromise ;
1796+ return { content : [ { type : 'text' , text : 'Final result' } ] } ;
1797+ } ) ;
1798+
1799+ // Initialize to get session ID
1800+ const initResponse = await sendPostRequest ( baseUrl , TEST_MESSAGES . initialize ) ;
1801+ sessionId = initResponse . headers . get ( 'mcp-session-id' ) as string ;
1802+ expect ( sessionId ) . toBeDefined ( ) ;
1803+
1804+ // Send a POST request for the tool
1805+ const toolCallRequest : JSONRPCMessage = {
1806+ jsonrpc : '2.0' ,
1807+ id : 100 ,
1808+ method : 'tools/call' ,
1809+ params : { name : 'polling-tool' , arguments : { } }
1810+ } ;
1811+
1812+ const postResponse = await fetch ( baseUrl , {
1813+ method : 'POST' ,
1814+ headers : {
1815+ 'Content-Type' : 'application/json' ,
1816+ Accept : 'text/event-stream, application/json' ,
1817+ 'mcp-session-id' : sessionId ,
1818+ 'mcp-protocol-version' : '2025-03-26'
1819+ } ,
1820+ body : JSON . stringify ( toolCallRequest )
1821+ } ) ;
1822+
1823+ expect ( postResponse . status ) . toBe ( 200 ) ;
1824+ expect ( postResponse . headers . get ( 'content-type' ) ) . toBe ( 'text/event-stream' ) ;
1825+
1826+ const reader = postResponse . body ?. getReader ( ) ;
1827+
1828+ // Read the priming event and extract event ID
1829+ const { value : primingValue } = await reader ! . read ( ) ;
1830+ const primingText = new TextDecoder ( ) . decode ( primingValue ) ;
1831+ expect ( primingText ) . toContain ( 'id: ' ) ;
1832+ expect ( primingText ) . toContain ( 'retry: 1000' ) ;
1833+
1834+ // Extract the priming event ID
1835+ const primingIdMatch = primingText . match ( / i d : ( [ ^ \n ] + ) / ) ;
1836+ expect ( primingIdMatch ) . toBeTruthy ( ) ;
1837+ const primingEventId = primingIdMatch ! [ 1 ] ;
1838+
1839+ // Server closes the stream to trigger polling
1840+ transport . closeSSEStream ( 100 ) ;
1841+
1842+ // Verify stream is closed
1843+ const { done } = await reader ! . read ( ) ;
1844+ expect ( done ) . toBe ( true ) ;
1845+
1846+ // Now complete the tool - this will store the result event
1847+ toolResolve ! ( ) ;
1848+
1849+ // Give the tool time to complete and store the result
1850+ await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
1851+
1852+ // Client reconnects with Last-Event-ID to get missed events
1853+ const reconnectResponse = await fetch ( baseUrl , {
1854+ method : 'GET' ,
1855+ headers : {
1856+ Accept : 'text/event-stream' ,
1857+ 'mcp-session-id' : sessionId ,
1858+ 'mcp-protocol-version' : '2025-03-26' ,
1859+ 'last-event-id' : primingEventId
1860+ }
1861+ } ) ;
1862+
1863+ expect ( reconnectResponse . status ) . toBe ( 200 ) ;
1864+ expect ( reconnectResponse . headers . get ( 'content-type' ) ) . toBe ( 'text/event-stream' ) ;
1865+
1866+ // Read the replayed events
1867+ const reconnectReader = reconnectResponse . body ?. getReader ( ) ;
1868+ const { value : replayValue } = await reconnectReader ! . read ( ) ;
1869+ const replayText = new TextDecoder ( ) . decode ( replayValue ) ;
1870+
1871+ // Should receive the tool result that was stored after stream was closed
1872+ expect ( replayText ) . toContain ( 'Final result' ) ;
1873+ expect ( replayText ) . toContain ( '"id":100' ) ;
1874+ } ) ;
17741875} ) ;
17751876
17761877// Test onsessionclosed callback
0 commit comments