diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index c8df2def36a..c965205b4e5 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -217,8 +217,8 @@ # Pinned GitHub Actions: # - actions/checkout@v5 (93cb6efe18208431cddfb8368fd83d5badbf9bfd) # https://github.com/actions/checkout/commit/93cb6efe18208431cddfb8368fd83d5badbf9bfd -# - actions/create-github-app-token@v2 (29824e69f54612133e76f7eaac726eef6c875baf) -# https://github.com/actions/create-github-app-token/commit/29824e69f54612133e76f7eaac726eef6c875baf +# - actions/create-github-app-token@v2 (7e473efe3cb98aa54f8d4bac15400b15fad77d94) +# https://github.com/actions/create-github-app-token/commit/7e473efe3cb98aa54f8d4bac15400b15fad77d94 # - actions/download-artifact@v6 (018cc2cf5baa6db3ef3c5f8a56943fffe632ef53) # https://github.com/actions/download-artifact/commit/018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # - actions/github-script@v8 (ed597411d8f924073f98dfc5c65a23a2325f34cd) @@ -5090,7 +5090,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} @@ -5583,7 +5583,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 006644820af..c9bec482cff 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -315,8 +315,8 @@ # Pinned GitHub Actions: # - actions/checkout@v5 (93cb6efe18208431cddfb8368fd83d5badbf9bfd) # https://github.com/actions/checkout/commit/93cb6efe18208431cddfb8368fd83d5badbf9bfd -# - actions/create-github-app-token@v2 (29824e69f54612133e76f7eaac726eef6c875baf) -# https://github.com/actions/create-github-app-token/commit/29824e69f54612133e76f7eaac726eef6c875baf +# - actions/create-github-app-token@v2 (7e473efe3cb98aa54f8d4bac15400b15fad77d94) +# https://github.com/actions/create-github-app-token/commit/7e473efe3cb98aa54f8d4bac15400b15fad77d94 # - actions/download-artifact@v6 (018cc2cf5baa6db3ef3c5f8a56943fffe632ef53) # https://github.com/actions/download-artifact/commit/018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # - actions/github-script@v8 (ed597411d8f924073f98dfc5c65a23a2325f34cd) @@ -5576,7 +5576,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} @@ -6361,7 +6361,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} @@ -6875,7 +6875,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index 45ef7c67043..24ccdbc788c 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -2582,94 +2582,145 @@ jobs: async function startHttpServer(configPath, options = {}) { const port = options.port || 3000; const stateless = options.stateless || false; - const { server, config, logger } = createMCPServer(configPath, { logDir: options.logDir }); - logger.debug(`Starting HTTP server on port ${port}`); + logger.debug = msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup] ${msg}\n`); + }; + logger.debug(`=== Starting Safe Inputs MCP HTTP Server ===`); + logger.debug(`Configuration file: ${configPath}`); + logger.debug(`Port: ${port}`); logger.debug(`Mode: ${stateless ? "stateless" : "stateful"}`); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: stateless ? undefined : () => randomUUID(), - enableJsonResponse: true, - enableDnsRebindingProtection: false, - }); - await server.connect(transport); - const httpServer = http.createServer(async (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } - if (req.method !== "POST") { - res.writeHead(405, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Method not allowed" })); - return; - } - try { - let body = null; - if (req.method === "POST") { - const chunks = []; - for await (const chunk of req) { - chunks.push(chunk); + logger.debug(`Environment: NODE_VERSION=${process.version}, PLATFORM=${process.platform}`); + try { + const { server, config, logger: mcpLogger } = createMCPServer(configPath, { logDir: options.logDir }); + Object.assign(logger, mcpLogger); + logger.debug(`MCP server created successfully`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools configured: ${config.tools.length}`); + logger.debug(`Creating HTTP transport...`); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: stateless ? undefined : () => randomUUID(), + enableJsonResponse: true, + enableDnsRebindingProtection: false, + }); + logger.debug(`HTTP transport created`); + logger.debug(`Connecting server to transport...`); + await server.connect(transport); + logger.debug(`Server connected to transport successfully`); + logger.debug(`Creating HTTP server...`); + const httpServer = http.createServer(async (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + return; + } + try { + let body = null; + if (req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const bodyStr = Buffer.concat(chunks).toString(); + try { + body = bodyStr ? JSON.parse(bodyStr) : null; + } catch (parseError) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error: Invalid JSON in request body", + }, + id: null, + }) + ); + return; + } } - const bodyStr = Buffer.concat(chunks).toString(); - try { - body = bodyStr ? JSON.parse(bodyStr) : null; - } catch (parseError) { - res.writeHead(400, { "Content-Type": "application/json" }); + await transport.handleRequest(req, res, body); + } catch (error) { + logger.debugError("Error handling request: ", error); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ jsonrpc: "2.0", error: { - code: -32700, - message: "Parse error: Invalid JSON in request body", + code: -32603, + message: error instanceof Error ? error.message : String(error), }, id: null, }) ); - return; } } - await transport.handleRequest(req, res, body); - } catch (error) { - logger.debugError("Error handling request: ", error); - if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32603, - message: error instanceof Error ? error.message : String(error), - }, - id: null, - }) - ); + }); + logger.debug(`Attempting to bind to port ${port}...`); + httpServer.listen(port, () => { + logger.debug(`=== Safe Inputs MCP HTTP Server Started Successfully ===`); + logger.debug(`HTTP server listening on http://localhost:${port}`); + logger.debug(`MCP endpoint: POST http://localhost:${port}/`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools available: ${config.tools.length}`); + logger.debug(`Server is ready to accept requests`); + }); + httpServer.on("error", error => { + if (error.code === "EADDRINUSE") { + logger.debugError(`ERROR: Port ${port} is already in use. `, error); + } else if (error.code === "EACCES") { + logger.debugError(`ERROR: Permission denied to bind to port ${port}. `, error); + } else { + logger.debugError(`ERROR: Failed to start HTTP server: `, error); } - } - }); - httpServer.listen(port, () => { - logger.debug(`HTTP server listening on http://localhost:${port}`); - logger.debug(`MCP endpoint: POST http://localhost:${port}/`); - logger.debug(`Server name: ${config.serverName || "safeinputs"}`); - logger.debug(`Server version: ${config.version || "1.0.0"}`); - logger.debug(`Tools available: ${config.tools.length}`); - }); - process.on("SIGINT", () => { - logger.debug("Received SIGINT, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.exit(1); }); - }); - process.on("SIGTERM", () => { - logger.debug("Received SIGTERM, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.on("SIGINT", () => { + logger.debug("Received SIGINT, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - return httpServer; + process.on("SIGTERM", () => { + logger.debug("Received SIGTERM, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); + }); + return httpServer; + } catch (error) { + const errorLogger = { + debug: msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup-error] ${msg}\n`); + }, + }; + errorLogger.debug(`=== FATAL ERROR: Failed to start Safe Inputs MCP HTTP Server ===`); + errorLogger.debug(`Error type: ${error.constructor.name}`); + errorLogger.debug(`Error message: ${error.message}`); + if (error.stack) { + errorLogger.debug(`Stack trace:\n${error.stack}`); + } + if (error.code) { + errorLogger.debug(`Error code: ${error.code}`); + } + errorLogger.debug(`Configuration file: ${configPath}`); + errorLogger.debug(`Port: ${port}`); + throw error; + } } if (require.main === module) { const args = process.argv.slice(2); @@ -2790,28 +2841,69 @@ jobs: export GH_AW_GH_TOKEN="${GH_AW_GH_TOKEN}" # Install MCP SDK package + echo "Installing @modelcontextprotocol/sdk..." cd /tmp/gh-aw/safe-inputs - if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0; then + if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0 2>&1; then echo "ERROR: Failed to install @modelcontextprotocol/sdk@1.24.0" + echo "npm install failed with exit code $?" + exit 1 + fi + echo "Successfully installed @modelcontextprotocol/sdk@1.24.0" + + # Verify required files exist + echo "Verifying safe-inputs setup..." + if [ ! -f mcp-server.cjs ]; then + echo "ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ + exit 1 + fi + if [ ! -f tools.json ]; then + echo "ERROR: tools.json not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ exit 1 fi + echo "Configuration files verified" + + # Log environment configuration + echo "Server configuration:" + echo " Port: $GH_AW_SAFE_INPUTS_PORT" + echo " API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}..." + echo " Working directory: $(pwd)" # Start the HTTP server in the background + echo "Starting safe-inputs MCP HTTP server..." node mcp-server.cjs > /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 & SERVER_PID=$! echo "Started safe-inputs MCP server with PID $SERVER_PID" # Wait for server to be ready (max 10 seconds) + echo "Waiting for server to become ready..." for i in {1..10}; do + # Check if process is still running + if ! kill -0 $SERVER_PID 2>/dev/null; then + echo "ERROR: Server process $SERVER_PID has died" + echo "Server log contents:" + cat /tmp/gh-aw/safe-inputs/logs/server.log + exit 1 + fi + + # Check if server is responding if curl -s -f -H "Authorization: Bearer $GH_AW_SAFE_INPUTS_API_KEY" http://localhost:$GH_AW_SAFE_INPUTS_PORT/ > /dev/null 2>&1; then - echo "Safe Inputs MCP server is ready" + echo "Safe Inputs MCP server is ready (attempt $i/10)" break fi + if [ $i -eq 10 ]; then - echo "ERROR: Safe Inputs MCP server failed to start" + echo "ERROR: Safe Inputs MCP server failed to start after 10 seconds" + echo "Process status: $(ps aux | grep '[m]cp-server.cjs' || echo 'not running')" + echo "Server log contents:" cat /tmp/gh-aw/safe-inputs/logs/server.log + echo "Checking port availability:" + netstat -tuln | grep $GH_AW_SAFE_INPUTS_PORT || echo "Port $GH_AW_SAFE_INPUTS_PORT not listening" exit 1 fi + + echo "Waiting for server... (attempt $i/10)" sleep 1 done diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index a92532d9bfc..b39872c5e69 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -379,8 +379,8 @@ # Pinned GitHub Actions: # - actions/checkout@v5 (93cb6efe18208431cddfb8368fd83d5badbf9bfd) # https://github.com/actions/checkout/commit/93cb6efe18208431cddfb8368fd83d5badbf9bfd -# - actions/create-github-app-token@v2 (29824e69f54612133e76f7eaac726eef6c875baf) -# https://github.com/actions/create-github-app-token/commit/29824e69f54612133e76f7eaac726eef6c875baf +# - actions/create-github-app-token@v2 (7e473efe3cb98aa54f8d4bac15400b15fad77d94) +# https://github.com/actions/create-github-app-token/commit/7e473efe3cb98aa54f8d4bac15400b15fad77d94 # - actions/download-artifact@v6 (018cc2cf5baa6db3ef3c5f8a56943fffe632ef53) # https://github.com/actions/download-artifact/commit/018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # - actions/github-script@v8 (ed597411d8f924073f98dfc5c65a23a2325f34cd) @@ -4921,7 +4921,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} @@ -5418,7 +5418,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index edf7d9a9afb..1764759b5a5 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -3165,94 +3165,145 @@ jobs: async function startHttpServer(configPath, options = {}) { const port = options.port || 3000; const stateless = options.stateless || false; - const { server, config, logger } = createMCPServer(configPath, { logDir: options.logDir }); - logger.debug(`Starting HTTP server on port ${port}`); + logger.debug = msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup] ${msg}\n`); + }; + logger.debug(`=== Starting Safe Inputs MCP HTTP Server ===`); + logger.debug(`Configuration file: ${configPath}`); + logger.debug(`Port: ${port}`); logger.debug(`Mode: ${stateless ? "stateless" : "stateful"}`); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: stateless ? undefined : () => randomUUID(), - enableJsonResponse: true, - enableDnsRebindingProtection: false, - }); - await server.connect(transport); - const httpServer = http.createServer(async (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } - if (req.method !== "POST") { - res.writeHead(405, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Method not allowed" })); - return; - } - try { - let body = null; - if (req.method === "POST") { - const chunks = []; - for await (const chunk of req) { - chunks.push(chunk); + logger.debug(`Environment: NODE_VERSION=${process.version}, PLATFORM=${process.platform}`); + try { + const { server, config, logger: mcpLogger } = createMCPServer(configPath, { logDir: options.logDir }); + Object.assign(logger, mcpLogger); + logger.debug(`MCP server created successfully`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools configured: ${config.tools.length}`); + logger.debug(`Creating HTTP transport...`); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: stateless ? undefined : () => randomUUID(), + enableJsonResponse: true, + enableDnsRebindingProtection: false, + }); + logger.debug(`HTTP transport created`); + logger.debug(`Connecting server to transport...`); + await server.connect(transport); + logger.debug(`Server connected to transport successfully`); + logger.debug(`Creating HTTP server...`); + const httpServer = http.createServer(async (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + return; + } + try { + let body = null; + if (req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const bodyStr = Buffer.concat(chunks).toString(); + try { + body = bodyStr ? JSON.parse(bodyStr) : null; + } catch (parseError) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error: Invalid JSON in request body", + }, + id: null, + }) + ); + return; + } } - const bodyStr = Buffer.concat(chunks).toString(); - try { - body = bodyStr ? JSON.parse(bodyStr) : null; - } catch (parseError) { - res.writeHead(400, { "Content-Type": "application/json" }); + await transport.handleRequest(req, res, body); + } catch (error) { + logger.debugError("Error handling request: ", error); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ jsonrpc: "2.0", error: { - code: -32700, - message: "Parse error: Invalid JSON in request body", + code: -32603, + message: error instanceof Error ? error.message : String(error), }, id: null, }) ); - return; } } - await transport.handleRequest(req, res, body); - } catch (error) { - logger.debugError("Error handling request: ", error); - if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32603, - message: error instanceof Error ? error.message : String(error), - }, - id: null, - }) - ); + }); + logger.debug(`Attempting to bind to port ${port}...`); + httpServer.listen(port, () => { + logger.debug(`=== Safe Inputs MCP HTTP Server Started Successfully ===`); + logger.debug(`HTTP server listening on http://localhost:${port}`); + logger.debug(`MCP endpoint: POST http://localhost:${port}/`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools available: ${config.tools.length}`); + logger.debug(`Server is ready to accept requests`); + }); + httpServer.on("error", error => { + if (error.code === "EADDRINUSE") { + logger.debugError(`ERROR: Port ${port} is already in use. `, error); + } else if (error.code === "EACCES") { + logger.debugError(`ERROR: Permission denied to bind to port ${port}. `, error); + } else { + logger.debugError(`ERROR: Failed to start HTTP server: `, error); } - } - }); - httpServer.listen(port, () => { - logger.debug(`HTTP server listening on http://localhost:${port}`); - logger.debug(`MCP endpoint: POST http://localhost:${port}/`); - logger.debug(`Server name: ${config.serverName || "safeinputs"}`); - logger.debug(`Server version: ${config.version || "1.0.0"}`); - logger.debug(`Tools available: ${config.tools.length}`); - }); - process.on("SIGINT", () => { - logger.debug("Received SIGINT, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.exit(1); }); - }); - process.on("SIGTERM", () => { - logger.debug("Received SIGTERM, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.on("SIGINT", () => { + logger.debug("Received SIGINT, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - return httpServer; + process.on("SIGTERM", () => { + logger.debug("Received SIGTERM, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); + }); + return httpServer; + } catch (error) { + const errorLogger = { + debug: msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup-error] ${msg}\n`); + }, + }; + errorLogger.debug(`=== FATAL ERROR: Failed to start Safe Inputs MCP HTTP Server ===`); + errorLogger.debug(`Error type: ${error.constructor.name}`); + errorLogger.debug(`Error message: ${error.message}`); + if (error.stack) { + errorLogger.debug(`Stack trace:\n${error.stack}`); + } + if (error.code) { + errorLogger.debug(`Error code: ${error.code}`); + } + errorLogger.debug(`Configuration file: ${configPath}`); + errorLogger.debug(`Port: ${port}`); + throw error; + } } if (require.main === module) { const args = process.argv.slice(2); @@ -3664,28 +3715,69 @@ jobs: export GH_TOKEN="${GH_TOKEN}" # Install MCP SDK package + echo "Installing @modelcontextprotocol/sdk..." cd /tmp/gh-aw/safe-inputs - if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0; then + if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0 2>&1; then echo "ERROR: Failed to install @modelcontextprotocol/sdk@1.24.0" + echo "npm install failed with exit code $?" + exit 1 + fi + echo "Successfully installed @modelcontextprotocol/sdk@1.24.0" + + # Verify required files exist + echo "Verifying safe-inputs setup..." + if [ ! -f mcp-server.cjs ]; then + echo "ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ + exit 1 + fi + if [ ! -f tools.json ]; then + echo "ERROR: tools.json not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ exit 1 fi + echo "Configuration files verified" + + # Log environment configuration + echo "Server configuration:" + echo " Port: $GH_AW_SAFE_INPUTS_PORT" + echo " API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}..." + echo " Working directory: $(pwd)" # Start the HTTP server in the background + echo "Starting safe-inputs MCP HTTP server..." node mcp-server.cjs > /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 & SERVER_PID=$! echo "Started safe-inputs MCP server with PID $SERVER_PID" # Wait for server to be ready (max 10 seconds) + echo "Waiting for server to become ready..." for i in {1..10}; do + # Check if process is still running + if ! kill -0 $SERVER_PID 2>/dev/null; then + echo "ERROR: Server process $SERVER_PID has died" + echo "Server log contents:" + cat /tmp/gh-aw/safe-inputs/logs/server.log + exit 1 + fi + + # Check if server is responding if curl -s -f -H "Authorization: Bearer $GH_AW_SAFE_INPUTS_API_KEY" http://localhost:$GH_AW_SAFE_INPUTS_PORT/ > /dev/null 2>&1; then - echo "Safe Inputs MCP server is ready" + echo "Safe Inputs MCP server is ready (attempt $i/10)" break fi + if [ $i -eq 10 ]; then - echo "ERROR: Safe Inputs MCP server failed to start" + echo "ERROR: Safe Inputs MCP server failed to start after 10 seconds" + echo "Process status: $(ps aux | grep '[m]cp-server.cjs' || echo 'not running')" + echo "Server log contents:" cat /tmp/gh-aw/safe-inputs/logs/server.log + echo "Checking port availability:" + netstat -tuln | grep $GH_AW_SAFE_INPUTS_PORT || echo "Port $GH_AW_SAFE_INPUTS_PORT not listening" exit 1 fi + + echo "Waiting for server... (attempt $i/10)" sleep 1 done diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index e5ee7e5695c..0a6133c4160 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1171,94 +1171,145 @@ jobs: async function startHttpServer(configPath, options = {}) { const port = options.port || 3000; const stateless = options.stateless || false; - const { server, config, logger } = createMCPServer(configPath, { logDir: options.logDir }); - logger.debug(`Starting HTTP server on port ${port}`); + logger.debug = msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup] ${msg}\n`); + }; + logger.debug(`=== Starting Safe Inputs MCP HTTP Server ===`); + logger.debug(`Configuration file: ${configPath}`); + logger.debug(`Port: ${port}`); logger.debug(`Mode: ${stateless ? "stateless" : "stateful"}`); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: stateless ? undefined : () => randomUUID(), - enableJsonResponse: true, - enableDnsRebindingProtection: false, - }); - await server.connect(transport); - const httpServer = http.createServer(async (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } - if (req.method !== "POST") { - res.writeHead(405, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Method not allowed" })); - return; - } - try { - let body = null; - if (req.method === "POST") { - const chunks = []; - for await (const chunk of req) { - chunks.push(chunk); + logger.debug(`Environment: NODE_VERSION=${process.version}, PLATFORM=${process.platform}`); + try { + const { server, config, logger: mcpLogger } = createMCPServer(configPath, { logDir: options.logDir }); + Object.assign(logger, mcpLogger); + logger.debug(`MCP server created successfully`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools configured: ${config.tools.length}`); + logger.debug(`Creating HTTP transport...`); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: stateless ? undefined : () => randomUUID(), + enableJsonResponse: true, + enableDnsRebindingProtection: false, + }); + logger.debug(`HTTP transport created`); + logger.debug(`Connecting server to transport...`); + await server.connect(transport); + logger.debug(`Server connected to transport successfully`); + logger.debug(`Creating HTTP server...`); + const httpServer = http.createServer(async (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + return; + } + try { + let body = null; + if (req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const bodyStr = Buffer.concat(chunks).toString(); + try { + body = bodyStr ? JSON.parse(bodyStr) : null; + } catch (parseError) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error: Invalid JSON in request body", + }, + id: null, + }) + ); + return; + } } - const bodyStr = Buffer.concat(chunks).toString(); - try { - body = bodyStr ? JSON.parse(bodyStr) : null; - } catch (parseError) { - res.writeHead(400, { "Content-Type": "application/json" }); + await transport.handleRequest(req, res, body); + } catch (error) { + logger.debugError("Error handling request: ", error); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ jsonrpc: "2.0", error: { - code: -32700, - message: "Parse error: Invalid JSON in request body", + code: -32603, + message: error instanceof Error ? error.message : String(error), }, id: null, }) ); - return; } } - await transport.handleRequest(req, res, body); - } catch (error) { - logger.debugError("Error handling request: ", error); - if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32603, - message: error instanceof Error ? error.message : String(error), - }, - id: null, - }) - ); + }); + logger.debug(`Attempting to bind to port ${port}...`); + httpServer.listen(port, () => { + logger.debug(`=== Safe Inputs MCP HTTP Server Started Successfully ===`); + logger.debug(`HTTP server listening on http://localhost:${port}`); + logger.debug(`MCP endpoint: POST http://localhost:${port}/`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools available: ${config.tools.length}`); + logger.debug(`Server is ready to accept requests`); + }); + httpServer.on("error", error => { + if (error.code === "EADDRINUSE") { + logger.debugError(`ERROR: Port ${port} is already in use. `, error); + } else if (error.code === "EACCES") { + logger.debugError(`ERROR: Permission denied to bind to port ${port}. `, error); + } else { + logger.debugError(`ERROR: Failed to start HTTP server: `, error); } - } - }); - httpServer.listen(port, () => { - logger.debug(`HTTP server listening on http://localhost:${port}`); - logger.debug(`MCP endpoint: POST http://localhost:${port}/`); - logger.debug(`Server name: ${config.serverName || "safeinputs"}`); - logger.debug(`Server version: ${config.version || "1.0.0"}`); - logger.debug(`Tools available: ${config.tools.length}`); - }); - process.on("SIGINT", () => { - logger.debug("Received SIGINT, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.exit(1); }); - }); - process.on("SIGTERM", () => { - logger.debug("Received SIGTERM, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.on("SIGINT", () => { + logger.debug("Received SIGINT, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - return httpServer; + process.on("SIGTERM", () => { + logger.debug("Received SIGTERM, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); + }); + return httpServer; + } catch (error) { + const errorLogger = { + debug: msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup-error] ${msg}\n`); + }, + }; + errorLogger.debug(`=== FATAL ERROR: Failed to start Safe Inputs MCP HTTP Server ===`); + errorLogger.debug(`Error type: ${error.constructor.name}`); + errorLogger.debug(`Error message: ${error.message}`); + if (error.stack) { + errorLogger.debug(`Stack trace:\n${error.stack}`); + } + if (error.code) { + errorLogger.debug(`Error code: ${error.code}`); + } + errorLogger.debug(`Configuration file: ${configPath}`); + errorLogger.debug(`Port: ${port}`); + throw error; + } } if (require.main === module) { const args = process.argv.slice(2); @@ -1379,28 +1430,69 @@ jobs: export GH_AW_GH_TOKEN="${GH_AW_GH_TOKEN}" # Install MCP SDK package + echo "Installing @modelcontextprotocol/sdk..." cd /tmp/gh-aw/safe-inputs - if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0; then + if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0 2>&1; then echo "ERROR: Failed to install @modelcontextprotocol/sdk@1.24.0" + echo "npm install failed with exit code $?" + exit 1 + fi + echo "Successfully installed @modelcontextprotocol/sdk@1.24.0" + + # Verify required files exist + echo "Verifying safe-inputs setup..." + if [ ! -f mcp-server.cjs ]; then + echo "ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ + exit 1 + fi + if [ ! -f tools.json ]; then + echo "ERROR: tools.json not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ exit 1 fi + echo "Configuration files verified" + + # Log environment configuration + echo "Server configuration:" + echo " Port: $GH_AW_SAFE_INPUTS_PORT" + echo " API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}..." + echo " Working directory: $(pwd)" # Start the HTTP server in the background + echo "Starting safe-inputs MCP HTTP server..." node mcp-server.cjs > /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 & SERVER_PID=$! echo "Started safe-inputs MCP server with PID $SERVER_PID" # Wait for server to be ready (max 10 seconds) + echo "Waiting for server to become ready..." for i in {1..10}; do + # Check if process is still running + if ! kill -0 $SERVER_PID 2>/dev/null; then + echo "ERROR: Server process $SERVER_PID has died" + echo "Server log contents:" + cat /tmp/gh-aw/safe-inputs/logs/server.log + exit 1 + fi + + # Check if server is responding if curl -s -f -H "Authorization: Bearer $GH_AW_SAFE_INPUTS_API_KEY" http://localhost:$GH_AW_SAFE_INPUTS_PORT/ > /dev/null 2>&1; then - echo "Safe Inputs MCP server is ready" + echo "Safe Inputs MCP server is ready (attempt $i/10)" break fi + if [ $i -eq 10 ]; then - echo "ERROR: Safe Inputs MCP server failed to start" + echo "ERROR: Safe Inputs MCP server failed to start after 10 seconds" + echo "Process status: $(ps aux | grep '[m]cp-server.cjs' || echo 'not running')" + echo "Server log contents:" cat /tmp/gh-aw/safe-inputs/logs/server.log + echo "Checking port availability:" + netstat -tuln | grep $GH_AW_SAFE_INPUTS_PORT || echo "Port $GH_AW_SAFE_INPUTS_PORT not listening" exit 1 fi + + echo "Waiting for server... (attempt $i/10)" sleep 1 done diff --git a/.github/workflows/test-python-safe-input.lock.yml b/.github/workflows/test-python-safe-input.lock.yml index 7f5cb190d8d..7dd0f3e8bc8 100644 --- a/.github/workflows/test-python-safe-input.lock.yml +++ b/.github/workflows/test-python-safe-input.lock.yml @@ -2354,94 +2354,145 @@ jobs: async function startHttpServer(configPath, options = {}) { const port = options.port || 3000; const stateless = options.stateless || false; - const { server, config, logger } = createMCPServer(configPath, { logDir: options.logDir }); - logger.debug(`Starting HTTP server on port ${port}`); + logger.debug = msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup] ${msg}\n`); + }; + logger.debug(`=== Starting Safe Inputs MCP HTTP Server ===`); + logger.debug(`Configuration file: ${configPath}`); + logger.debug(`Port: ${port}`); logger.debug(`Mode: ${stateless ? "stateless" : "stateful"}`); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: stateless ? undefined : () => randomUUID(), - enableJsonResponse: true, - enableDnsRebindingProtection: false, - }); - await server.connect(transport); - const httpServer = http.createServer(async (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } - if (req.method !== "POST") { - res.writeHead(405, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Method not allowed" })); - return; - } - try { - let body = null; - if (req.method === "POST") { - const chunks = []; - for await (const chunk of req) { - chunks.push(chunk); + logger.debug(`Environment: NODE_VERSION=${process.version}, PLATFORM=${process.platform}`); + try { + const { server, config, logger: mcpLogger } = createMCPServer(configPath, { logDir: options.logDir }); + Object.assign(logger, mcpLogger); + logger.debug(`MCP server created successfully`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools configured: ${config.tools.length}`); + logger.debug(`Creating HTTP transport...`); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: stateless ? undefined : () => randomUUID(), + enableJsonResponse: true, + enableDnsRebindingProtection: false, + }); + logger.debug(`HTTP transport created`); + logger.debug(`Connecting server to transport...`); + await server.connect(transport); + logger.debug(`Server connected to transport successfully`); + logger.debug(`Creating HTTP server...`); + const httpServer = http.createServer(async (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + return; + } + try { + let body = null; + if (req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const bodyStr = Buffer.concat(chunks).toString(); + try { + body = bodyStr ? JSON.parse(bodyStr) : null; + } catch (parseError) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error: Invalid JSON in request body", + }, + id: null, + }) + ); + return; + } } - const bodyStr = Buffer.concat(chunks).toString(); - try { - body = bodyStr ? JSON.parse(bodyStr) : null; - } catch (parseError) { - res.writeHead(400, { "Content-Type": "application/json" }); + await transport.handleRequest(req, res, body); + } catch (error) { + logger.debugError("Error handling request: ", error); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ jsonrpc: "2.0", error: { - code: -32700, - message: "Parse error: Invalid JSON in request body", + code: -32603, + message: error instanceof Error ? error.message : String(error), }, id: null, }) ); - return; } } - await transport.handleRequest(req, res, body); - } catch (error) { - logger.debugError("Error handling request: ", error); - if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32603, - message: error instanceof Error ? error.message : String(error), - }, - id: null, - }) - ); + }); + logger.debug(`Attempting to bind to port ${port}...`); + httpServer.listen(port, () => { + logger.debug(`=== Safe Inputs MCP HTTP Server Started Successfully ===`); + logger.debug(`HTTP server listening on http://localhost:${port}`); + logger.debug(`MCP endpoint: POST http://localhost:${port}/`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools available: ${config.tools.length}`); + logger.debug(`Server is ready to accept requests`); + }); + httpServer.on("error", error => { + if (error.code === "EADDRINUSE") { + logger.debugError(`ERROR: Port ${port} is already in use. `, error); + } else if (error.code === "EACCES") { + logger.debugError(`ERROR: Permission denied to bind to port ${port}. `, error); + } else { + logger.debugError(`ERROR: Failed to start HTTP server: `, error); } - } - }); - httpServer.listen(port, () => { - logger.debug(`HTTP server listening on http://localhost:${port}`); - logger.debug(`MCP endpoint: POST http://localhost:${port}/`); - logger.debug(`Server name: ${config.serverName || "safeinputs"}`); - logger.debug(`Server version: ${config.version || "1.0.0"}`); - logger.debug(`Tools available: ${config.tools.length}`); - }); - process.on("SIGINT", () => { - logger.debug("Received SIGINT, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.exit(1); }); - }); - process.on("SIGTERM", () => { - logger.debug("Received SIGTERM, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.on("SIGINT", () => { + logger.debug("Received SIGINT, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - return httpServer; + process.on("SIGTERM", () => { + logger.debug("Received SIGTERM, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); + }); + return httpServer; + } catch (error) { + const errorLogger = { + debug: msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup-error] ${msg}\n`); + }, + }; + errorLogger.debug(`=== FATAL ERROR: Failed to start Safe Inputs MCP HTTP Server ===`); + errorLogger.debug(`Error type: ${error.constructor.name}`); + errorLogger.debug(`Error message: ${error.message}`); + if (error.stack) { + errorLogger.debug(`Stack trace:\n${error.stack}`); + } + if (error.code) { + errorLogger.debug(`Error code: ${error.code}`); + } + errorLogger.debug(`Configuration file: ${configPath}`); + errorLogger.debug(`Port: ${port}`); + throw error; + } } if (require.main === module) { const args = process.argv.slice(2); @@ -2655,28 +2706,69 @@ jobs: # Install MCP SDK package + echo "Installing @modelcontextprotocol/sdk..." cd /tmp/gh-aw/safe-inputs - if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0; then + if ! npm install --no-save @modelcontextprotocol/sdk@1.24.0 2>&1; then echo "ERROR: Failed to install @modelcontextprotocol/sdk@1.24.0" + echo "npm install failed with exit code $?" + exit 1 + fi + echo "Successfully installed @modelcontextprotocol/sdk@1.24.0" + + # Verify required files exist + echo "Verifying safe-inputs setup..." + if [ ! -f mcp-server.cjs ]; then + echo "ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ + exit 1 + fi + if [ ! -f tools.json ]; then + echo "ERROR: tools.json not found in /tmp/gh-aw/safe-inputs" + ls -la /tmp/gh-aw/safe-inputs/ exit 1 fi + echo "Configuration files verified" + + # Log environment configuration + echo "Server configuration:" + echo " Port: $GH_AW_SAFE_INPUTS_PORT" + echo " API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}..." + echo " Working directory: $(pwd)" # Start the HTTP server in the background + echo "Starting safe-inputs MCP HTTP server..." node mcp-server.cjs > /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 & SERVER_PID=$! echo "Started safe-inputs MCP server with PID $SERVER_PID" # Wait for server to be ready (max 10 seconds) + echo "Waiting for server to become ready..." for i in {1..10}; do + # Check if process is still running + if ! kill -0 $SERVER_PID 2>/dev/null; then + echo "ERROR: Server process $SERVER_PID has died" + echo "Server log contents:" + cat /tmp/gh-aw/safe-inputs/logs/server.log + exit 1 + fi + + # Check if server is responding if curl -s -f -H "Authorization: Bearer $GH_AW_SAFE_INPUTS_API_KEY" http://localhost:$GH_AW_SAFE_INPUTS_PORT/ > /dev/null 2>&1; then - echo "Safe Inputs MCP server is ready" + echo "Safe Inputs MCP server is ready (attempt $i/10)" break fi + if [ $i -eq 10 ]; then - echo "ERROR: Safe Inputs MCP server failed to start" + echo "ERROR: Safe Inputs MCP server failed to start after 10 seconds" + echo "Process status: $(ps aux | grep '[m]cp-server.cjs' || echo 'not running')" + echo "Server log contents:" cat /tmp/gh-aw/safe-inputs/logs/server.log + echo "Checking port availability:" + netstat -tuln | grep $GH_AW_SAFE_INPUTS_PORT || echo "Port $GH_AW_SAFE_INPUTS_PORT not listening" exit 1 fi + + echo "Waiting for server... (attempt $i/10)" sleep 1 done diff --git a/pkg/workflow/js/safe_inputs_mcp_server_http.cjs b/pkg/workflow/js/safe_inputs_mcp_server_http.cjs index 5a57fcbbdc7..596029d40a0 100644 --- a/pkg/workflow/js/safe_inputs_mcp_server_http.cjs +++ b/pkg/workflow/js/safe_inputs_mcp_server_http.cjs @@ -127,117 +127,177 @@ async function startHttpServer(configPath, options = {}) { const port = options.port || 3000; const stateless = options.stateless || false; - // Create the MCP server - const { server, config, logger } = createMCPServer(configPath, { logDir: options.logDir }); + logger.debug = msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup] ${msg}\n`); + }; - logger.debug(`Starting HTTP server on port ${port}`); + logger.debug(`=== Starting Safe Inputs MCP HTTP Server ===`); + logger.debug(`Configuration file: ${configPath}`); + logger.debug(`Port: ${port}`); logger.debug(`Mode: ${stateless ? "stateless" : "stateful"}`); + logger.debug(`Environment: NODE_VERSION=${process.version}, PLATFORM=${process.platform}`); - // Create the HTTP transport - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: stateless ? undefined : () => randomUUID(), - enableJsonResponse: true, - enableDnsRebindingProtection: false, // Disable for local development - }); + // Create the MCP server + try { + const { server, config, logger: mcpLogger } = createMCPServer(configPath, { logDir: options.logDir }); - // Connect server to transport - await server.connect(transport); - - // Create HTTP server - const httpServer = http.createServer(async (req, res) => { - // Set CORS headers for development - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - - // Handle OPTIONS preflight - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); - return; - } + // Use the MCP logger for subsequent messages + Object.assign(logger, mcpLogger); - // Only handle POST requests for MCP protocol - if (req.method !== "POST") { - res.writeHead(405, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Method not allowed" })); - return; - } + logger.debug(`MCP server created successfully`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools configured: ${config.tools.length}`); + + logger.debug(`Creating HTTP transport...`); + // Create the HTTP transport + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: stateless ? undefined : () => randomUUID(), + enableJsonResponse: true, + enableDnsRebindingProtection: false, // Disable for local development + }); + logger.debug(`HTTP transport created`); + + // Connect server to transport + logger.debug(`Connecting server to transport...`); + await server.connect(transport); + logger.debug(`Server connected to transport successfully`); + + // Create HTTP server + logger.debug(`Creating HTTP server...`); + const httpServer = http.createServer(async (req, res) => { + // Set CORS headers for development + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); + + // Handle OPTIONS preflight + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + + // Only handle POST requests for MCP protocol + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Method not allowed" })); + return; + } - try { - // Parse request body for POST requests - let body = null; - if (req.method === "POST") { - const chunks = []; - for await (const chunk of req) { - chunks.push(chunk); + try { + // Parse request body for POST requests + let body = null; + if (req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + const bodyStr = Buffer.concat(chunks).toString(); + try { + body = bodyStr ? JSON.parse(bodyStr) : null; + } catch (parseError) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32700, + message: "Parse error: Invalid JSON in request body", + }, + id: null, + }) + ); + return; + } } - const bodyStr = Buffer.concat(chunks).toString(); - try { - body = bodyStr ? JSON.parse(bodyStr) : null; - } catch (parseError) { - res.writeHead(400, { "Content-Type": "application/json" }); + + // Let the transport handle the request + await transport.handleRequest(req, res, body); + } catch (error) { + logger.debugError("Error handling request: ", error); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ jsonrpc: "2.0", error: { - code: -32700, - message: "Parse error: Invalid JSON in request body", + code: -32603, + message: error instanceof Error ? error.message : String(error), }, id: null, }) ); - return; } } + }); - // Let the transport handle the request - await transport.handleRequest(req, res, body); - } catch (error) { - logger.debugError("Error handling request: ", error); - if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32603, - message: error instanceof Error ? error.message : String(error), - }, - id: null, - }) - ); - } - } - }); + // Start listening + logger.debug(`Attempting to bind to port ${port}...`); + httpServer.listen(port, () => { + logger.debug(`=== Safe Inputs MCP HTTP Server Started Successfully ===`); + logger.debug(`HTTP server listening on http://localhost:${port}`); + logger.debug(`MCP endpoint: POST http://localhost:${port}/`); + logger.debug(`Server name: ${config.serverName || "safeinputs"}`); + logger.debug(`Server version: ${config.version || "1.0.0"}`); + logger.debug(`Tools available: ${config.tools.length}`); + logger.debug(`Server is ready to accept requests`); + }); - // Start listening - httpServer.listen(port, () => { - logger.debug(`HTTP server listening on http://localhost:${port}`); - logger.debug(`MCP endpoint: POST http://localhost:${port}/`); - logger.debug(`Server name: ${config.serverName || "safeinputs"}`); - logger.debug(`Server version: ${config.version || "1.0.0"}`); - logger.debug(`Tools available: ${config.tools.length}`); - }); + // Handle bind errors + httpServer.on("error", error => { + if (error.code === "EADDRINUSE") { + logger.debugError(`ERROR: Port ${port} is already in use. `, error); + } else if (error.code === "EACCES") { + logger.debugError(`ERROR: Permission denied to bind to port ${port}. `, error); + } else { + logger.debugError(`ERROR: Failed to start HTTP server: `, error); + } + process.exit(1); + }); - // Handle shutdown gracefully - process.on("SIGINT", () => { - logger.debug("Received SIGINT, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + // Handle shutdown gracefully + process.on("SIGINT", () => { + logger.debug("Received SIGINT, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - process.on("SIGTERM", () => { - logger.debug("Received SIGTERM, shutting down..."); - httpServer.close(() => { - logger.debug("HTTP server closed"); - process.exit(0); + process.on("SIGTERM", () => { + logger.debug("Received SIGTERM, shutting down..."); + httpServer.close(() => { + logger.debug("HTTP server closed"); + process.exit(0); + }); }); - }); - return httpServer; + return httpServer; + } catch (error) { + // Log detailed error information for startup failures + const errorLogger = { + debug: msg => { + const timestamp = new Date().toISOString(); + process.stderr.write(`[${timestamp}] [safe-inputs-startup-error] ${msg}\n`); + }, + }; + errorLogger.debug(`=== FATAL ERROR: Failed to start Safe Inputs MCP HTTP Server ===`); + errorLogger.debug(`Error type: ${error.constructor.name}`); + errorLogger.debug(`Error message: ${error.message}`); + if (error.stack) { + errorLogger.debug(`Stack trace:\n${error.stack}`); + } + if (error.code) { + errorLogger.debug(`Error code: ${error.code}`); + } + errorLogger.debug(`Configuration file: ${configPath}`); + errorLogger.debug(`Port: ${port}`); + + // Re-throw the error to be caught by the caller + throw error; + } } // If run directly, start the HTTP server with command-line arguments diff --git a/pkg/workflow/mcp_servers.go b/pkg/workflow/mcp_servers.go index 0126d4147b0..0fe9d240741 100644 --- a/pkg/workflow/mcp_servers.go +++ b/pkg/workflow/mcp_servers.go @@ -437,29 +437,73 @@ func (c *Compiler) generateMCPSetup(yaml *strings.Builder, tools map[string]any, yaml.WriteString(" \n") yaml.WriteString(" # Install MCP SDK package\n") + yaml.WriteString(" echo \"Installing @modelcontextprotocol/sdk...\"\n") yaml.WriteString(" cd /tmp/gh-aw/safe-inputs\n") - yaml.WriteString(fmt.Sprintf(" if ! npm install --no-save @modelcontextprotocol/sdk@%s; then\n", constants.DefaultMCPSDKVersion)) + yaml.WriteString(fmt.Sprintf(" if ! npm install --no-save @modelcontextprotocol/sdk@%s 2>&1; then\n", constants.DefaultMCPSDKVersion)) yaml.WriteString(fmt.Sprintf(" echo \"ERROR: Failed to install @modelcontextprotocol/sdk@%s\"\n", constants.DefaultMCPSDKVersion)) + yaml.WriteString(" echo \"npm install failed with exit code $?\"\n") yaml.WriteString(" exit 1\n") yaml.WriteString(" fi\n") + yaml.WriteString(fmt.Sprintf(" echo \"Successfully installed @modelcontextprotocol/sdk@%s\"\n", constants.DefaultMCPSDKVersion)) + yaml.WriteString(" \n") + + yaml.WriteString(" # Verify required files exist\n") + yaml.WriteString(" echo \"Verifying safe-inputs setup...\"\n") + yaml.WriteString(" if [ ! -f mcp-server.cjs ]; then\n") + yaml.WriteString(" echo \"ERROR: mcp-server.cjs not found in /tmp/gh-aw/safe-inputs\"\n") + yaml.WriteString(" ls -la /tmp/gh-aw/safe-inputs/\n") + yaml.WriteString(" exit 1\n") + yaml.WriteString(" fi\n") + yaml.WriteString(" if [ ! -f tools.json ]; then\n") + yaml.WriteString(" echo \"ERROR: tools.json not found in /tmp/gh-aw/safe-inputs\"\n") + yaml.WriteString(" ls -la /tmp/gh-aw/safe-inputs/\n") + yaml.WriteString(" exit 1\n") + yaml.WriteString(" fi\n") + yaml.WriteString(" echo \"Configuration files verified\"\n") + yaml.WriteString(" \n") + + yaml.WriteString(" # Log environment configuration\n") + yaml.WriteString(" echo \"Server configuration:\"\n") + yaml.WriteString(" echo \" Port: $GH_AW_SAFE_INPUTS_PORT\"\n") + yaml.WriteString(" echo \" API Key: ${GH_AW_SAFE_INPUTS_API_KEY:0:8}...\"\n") + yaml.WriteString(" echo \" Working directory: $(pwd)\"\n") yaml.WriteString(" \n") yaml.WriteString(" # Start the HTTP server in the background\n") + yaml.WriteString(" echo \"Starting safe-inputs MCP HTTP server...\"\n") yaml.WriteString(" node mcp-server.cjs > /tmp/gh-aw/safe-inputs/logs/server.log 2>&1 &\n") yaml.WriteString(" SERVER_PID=$!\n") yaml.WriteString(" echo \"Started safe-inputs MCP server with PID $SERVER_PID\"\n") yaml.WriteString(" \n") + yaml.WriteString(" # Wait for server to be ready (max 10 seconds)\n") + yaml.WriteString(" echo \"Waiting for server to become ready...\"\n") yaml.WriteString(" for i in {1..10}; do\n") + yaml.WriteString(" # Check if process is still running\n") + yaml.WriteString(" if ! kill -0 $SERVER_PID 2>/dev/null; then\n") + yaml.WriteString(" echo \"ERROR: Server process $SERVER_PID has died\"\n") + yaml.WriteString(" echo \"Server log contents:\"\n") + yaml.WriteString(" cat /tmp/gh-aw/safe-inputs/logs/server.log\n") + yaml.WriteString(" exit 1\n") + yaml.WriteString(" fi\n") + yaml.WriteString(" \n") + yaml.WriteString(" # Check if server is responding\n") yaml.WriteString(" if curl -s -f -H \"Authorization: Bearer $GH_AW_SAFE_INPUTS_API_KEY\" http://localhost:$GH_AW_SAFE_INPUTS_PORT/ > /dev/null 2>&1; then\n") - yaml.WriteString(" echo \"Safe Inputs MCP server is ready\"\n") + yaml.WriteString(" echo \"Safe Inputs MCP server is ready (attempt $i/10)\"\n") yaml.WriteString(" break\n") yaml.WriteString(" fi\n") + yaml.WriteString(" \n") yaml.WriteString(" if [ $i -eq 10 ]; then\n") - yaml.WriteString(" echo \"ERROR: Safe Inputs MCP server failed to start\"\n") + yaml.WriteString(" echo \"ERROR: Safe Inputs MCP server failed to start after 10 seconds\"\n") + yaml.WriteString(" echo \"Process status: $(ps aux | grep '[m]cp-server.cjs' || echo 'not running')\"\n") + yaml.WriteString(" echo \"Server log contents:\"\n") yaml.WriteString(" cat /tmp/gh-aw/safe-inputs/logs/server.log\n") + yaml.WriteString(" echo \"Checking port availability:\"\n") + yaml.WriteString(" netstat -tuln | grep $GH_AW_SAFE_INPUTS_PORT || echo \"Port $GH_AW_SAFE_INPUTS_PORT not listening\"\n") yaml.WriteString(" exit 1\n") yaml.WriteString(" fi\n") + yaml.WriteString(" \n") + yaml.WriteString(" echo \"Waiting for server... (attempt $i/10)\"\n") yaml.WriteString(" sleep 1\n") yaml.WriteString(" done\n") yaml.WriteString(" \n") diff --git a/pkg/workflow/safe_inputs_http_integration_test.go b/pkg/workflow/safe_inputs_http_integration_test.go index dda28941812..2a473b027dc 100644 --- a/pkg/workflow/safe_inputs_http_integration_test.go +++ b/pkg/workflow/safe_inputs_http_integration_test.go @@ -327,10 +327,10 @@ Test readiness check "for i in {1..10}; do", "if curl -s -f -H \"Authorization: Bearer", "http://localhost:$GH_AW_SAFE_INPUTS_PORT/", - "echo \"Safe Inputs MCP server is ready\"", + "Safe Inputs MCP server is ready", "break", "if [ $i -eq 10 ]; then", - "echo \"ERROR: Safe Inputs MCP server failed to start\"", + "ERROR: Safe Inputs MCP server failed to start after 10 seconds", "cat /tmp/gh-aw/safe-inputs/logs/server.log", "exit 1", "sleep 1",