Private/Start-McpHttpListener.ps1
|
function Start-McpHttpListener { <# .SYNOPSIS Runs the MCP server using the Streamable HTTP transport. .DESCRIPTION Opens an HttpListener on the supplied port and serves the MCP endpoint at /mcp/. Client-to-server JSON-RPC requests arrive as HTTP POST with a JSON body and the response is returned as application/json on the same POST, in accordance with the MCP Streamable HTTP transport spec. Notifications (requests without an id) receive HTTP 202 Accepted with an empty body. GET and DELETE are responded to with 405 Method Not Allowed because this implementation does not push server-initiated messages or track sessions. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$MCPRoot, [Parameter(Mandatory)] [string]$LogPath, [Parameter(Mandatory)] [int]$Port ) $listener = [System.Net.HttpListener]::new() $prefix = "http://localhost:$Port/mcp/" $listener.Prefixes.Add($prefix) $listener.Start() Add-Content -Path $LogPath -Value "HTTP listener started on $prefix" try { while ($listener.IsListening) { $context = $listener.GetContext() $request = $context.Request $response = $context.Response try { switch ($request.HttpMethod) { 'POST' { $reader = [System.IO.StreamReader]::new($request.InputStream, $request.ContentEncoding) try { $body = $reader.ReadToEnd() } finally { $reader.Dispose() } Add-Content -Path $LogPath -Value "Received request|$body" $rpcResponse = Invoke-JsonRpcRequest -RequestJson $body -MCPRoot $MCPRoot if ($null -eq $rpcResponse) { # Notification — acknowledge with 202 per Streamable HTTP spec $response.StatusCode = 202 $response.ContentLength64 = 0 } else { $responseJson = $rpcResponse | ConvertTo-Json -Depth 10 -Compress Add-Content -Path $LogPath -Value "Sending response|$responseJson" $buffer = [System.Text.Encoding]::UTF8.GetBytes($responseJson) $response.StatusCode = 200 $response.ContentType = 'application/json' $response.ContentLength64 = $buffer.Length $response.OutputStream.Write($buffer, 0, $buffer.Length) } } 'GET' { # Server-initiated SSE streams are not supported by this server. $response.StatusCode = 405 $response.AddHeader('Allow', 'POST') } 'DELETE' { # No session state to terminate. $response.StatusCode = 405 $response.AddHeader('Allow', 'POST') } 'OPTIONS' { $response.StatusCode = 204 $response.AddHeader('Access-Control-Allow-Methods', 'POST, OPTIONS') $response.AddHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id') } default { $response.StatusCode = 405 } } } catch { Add-Content -Path $LogPath -Value "Error handling HTTP request|$_" $response.StatusCode = 500 } finally { $response.Close() } } } finally { if ($listener.IsListening) { $listener.Stop() } $listener.Close() Add-Content -Path $LogPath -Value "HTTP listener stopped on $prefix" } } |