From ccc9c77294c91779ed44c7e80b06499b4c300b84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:07:17 +0000 Subject: [PATCH 1/6] Initial plan From 2af20c273419f2f6b7b35f5ee76517a0799c9332 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:20:27 +0000 Subject: [PATCH 2/6] feat: add stdio transport support to MCP client integration tests - Update client to support both stdio and http transport modes (MCP_MODE env var, default: stdio) - In stdio mode, client spawns server via StdioClientTransport from MCP SDK - Update run-integration-tests.sh to skip manual server start/stop in stdio mode - Add test:integration:http and test:integration:stdio scripts to client/package.json - Update CI workflow to test both transport modes via matrix strategy - Update docs/testing.md to document stdio as default transport - Update docs/getting-started.md and docs/public.md to recommend VSIX installation Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> --- .../workflows/client-integration-tests.yml | 10 ++-- client/package.json | 4 +- client/scripts/run-integration-tests.sh | 60 ++++++++++++------- client/src/ql-mcp-client.js | 37 ++++++++++-- docs/getting-started.md | 12 ++-- docs/public.md | 19 +++++- docs/testing.md | 11 +++- 7 files changed, 111 insertions(+), 42 deletions(-) diff --git a/.github/workflows/client-integration-tests.yml b/.github/workflows/client-integration-tests.yml index 2852723..991b595 100644 --- a/.github/workflows/client-integration-tests.yml +++ b/.github/workflows/client-integration-tests.yml @@ -26,17 +26,19 @@ permissions: jobs: integration-tests: - name: Integration Tests (${{ matrix.os }}) + name: Integration Tests (${{ matrix.os }}, ${{ matrix.mcp-mode }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: + mcp-mode: [http, stdio] os: [ubuntu-latest, windows-latest] env: HTTP_HOST: 'localhost' HTTP_PORT: '3000' + MCP_MODE: ${{ matrix.mcp-mode }} TIMEOUT_SECONDS: '30' URL_SCHEME: 'http' @@ -110,7 +112,7 @@ jobs: run: npm run test:integration --workspace=client - name: MCP Integration Tests - Stop the background MCP server process - if: always() + if: always() && matrix.mcp-mode == 'http' shell: bash run: | if [ -f server.pid ]; then @@ -141,8 +143,8 @@ jobs: - name: MCP Integration Tests - Summary shell: bash run: | - echo "## Integration Tests Summary (${{ matrix.os }})" >> $GITHUB_STEP_SUMMARY - echo "โœ… MCP server integration tests passed on ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY + echo "## Integration Tests Summary (${{ matrix.os }}, ${{ matrix.mcp-mode }})" >> $GITHUB_STEP_SUMMARY + echo "โœ… MCP server integration tests passed on ${{ matrix.os }} with ${{ matrix.mcp-mode }} transport" >> $GITHUB_STEP_SUMMARY codeql-path-tests: name: CODEQL_PATH Tests (${{ matrix.os }}) diff --git a/client/package.json b/client/package.json index 44175c6..d365715 100644 --- a/client/package.json +++ b/client/package.json @@ -55,8 +55,10 @@ "test:coverage": "echo 'NOOP client test:coverage'", "test:integration": "scripts/run-integration-tests.sh --no-install-packs", "test:integration:default": "ENABLE_MONITORING_TOOLS=false scripts/run-integration-tests.sh --no-install-packs", + "test:integration:http": "MCP_MODE=http scripts/run-integration-tests.sh --no-install-packs", "test:integration:install-packs": "scripts/run-integration-tests.sh", - "test:integration:monitoring": "ENABLE_MONITORING_TOOLS=true scripts/run-integration-tests.sh", + "test:integration:monitoring": "ENABLE_MONITORING_TOOLS=true scripts/run-integration-tests.sh --no-install-packs", + "test:integration:stdio": "MCP_MODE=stdio scripts/run-integration-tests.sh --no-install-packs", "tidy": "npm run lint && npm run format" } } diff --git a/client/scripts/run-integration-tests.sh b/client/scripts/run-integration-tests.sh index 7dc0942..a10e38f 100755 --- a/client/scripts/run-integration-tests.sh +++ b/client/scripts/run-integration-tests.sh @@ -8,8 +8,9 @@ # 2. Monitoring mode (monitoring tools enabled) - tests session_* tools # # Environment Variables: -# HTTP_HOST - Server host (default: localhost) -# HTTP_PORT - Server port (default: 3000) +# MCP_MODE - MCP transport mode (default: stdio, also: http) +# HTTP_HOST - Server host for HTTP mode (default: localhost) +# HTTP_PORT - Server port for HTTP mode (default: 3000) # TIMEOUT_SECONDS - Request timeout (default: 30) # ENABLE_MONITORING_TOOLS - Force a specific mode instead of running both: # "true" = only run with monitoring tools enabled @@ -20,6 +21,7 @@ # ./run-integration-tests.sh # Run in BOTH modes (recommended) # ENABLE_MONITORING_TOOLS=false ./run-integration-tests.sh # Only default mode # ENABLE_MONITORING_TOOLS=true ./run-integration-tests.sh # Only monitoring mode +# MCP_MODE=http ./run-integration-tests.sh # Run using HTTP transport # ./run-integration-tests.sh --tools session_end # Filter to specific tools set -e @@ -29,6 +31,7 @@ CLIENT_DIR="$(dirname "$SCRIPT_DIR")" SERVER_DIR="$(dirname "$CLIENT_DIR")/server" # Set default environment variables +export MCP_MODE="${MCP_MODE:-stdio}" export HTTP_HOST="${HTTP_HOST:-localhost}" export HTTP_PORT="${HTTP_PORT:-3000}" export TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-30}" @@ -65,7 +68,10 @@ for arg in "$@"; do done echo "๐Ÿš€ Starting CodeQL MCP Integration Tests" -echo "Server URL: $URL_SCHEME://$HTTP_HOST:$HTTP_PORT/mcp" +echo "MCP Mode: $MCP_MODE" +if [ "$MCP_MODE" = "http" ]; then + echo "Server URL: $URL_SCHEME://$HTTP_HOST:$HTTP_PORT/mcp" +fi # Step 1: Build and bundle the server code echo "๐Ÿ“ฆ Building CodeQL MCP server bundle..." @@ -81,43 +87,55 @@ else fi cd "$CLIENT_DIR" -export MCP_MODE=http -export MCP_SERVER_URL="$URL_SCHEME://$HTTP_HOST:$HTTP_PORT/mcp" + +# For HTTP mode, set the server URL for the client +if [ "$MCP_MODE" = "http" ]; then + export MCP_SERVER_URL="$URL_SCHEME://$HTTP_HOST:$HTTP_PORT/mcp" +fi # Function to run tests in a specific mode run_tests_in_mode() { local mode_name="$1" local enable_monitoring="$2" - + echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "๐Ÿงช Running integration tests: $mode_name" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" - + # Set the monitoring tools flag for this run export ENABLE_MONITORING_TOOLS="$enable_monitoring" - - # Start MCP server with current settings - echo "๐Ÿš€ Starting MCP server (monitoring=$enable_monitoring)..." - "$SCRIPT_DIR/start-server.sh" - - # Wait for server startup - echo "โณ Waiting for server startup..." - "$SCRIPT_DIR/wait-for-server.sh" - + + if [ "$MCP_MODE" = "http" ]; then + # HTTP mode: start server in background, run tests, stop server + echo "๐Ÿš€ Starting MCP server (monitoring=$enable_monitoring)..." + "$SCRIPT_DIR/start-server.sh" + + # Wait for server startup + echo "โณ Waiting for server startup..." + "$SCRIPT_DIR/wait-for-server.sh" + else + # stdio mode: client spawns server directly via StdioClientTransport + echo "๐Ÿ“ก Using stdio transport (client spawns server directly)" + fi + # Run the integration tests (skip pack installation since we already did it) echo "๐Ÿงช Running tests..." node src/ql-mcp-client.js integration-tests --no-install-packs "$@" - - # Stop the server before next mode - echo "๐Ÿ›‘ Stopping server..." - "$SCRIPT_DIR/stop-server.sh" + + if [ "$MCP_MODE" = "http" ]; then + # Stop the server before next mode + echo "๐Ÿ›‘ Stopping server..." + "$SCRIPT_DIR/stop-server.sh" + fi } # Trap to ensure cleanup happens even if script fails cleanup() { echo "๐Ÿงน Cleaning up..." - "$SCRIPT_DIR/stop-server.sh" 2>/dev/null || true + if [ "$MCP_MODE" = "http" ]; then + "$SCRIPT_DIR/stop-server.sh" 2>/dev/null || true + fi } trap cleanup EXIT diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index c00cfe4..74f70f2 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -8,6 +8,7 @@ /* global URL, setTimeout */ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { execSync } from "child_process"; import dotenv from "dotenv"; @@ -36,6 +37,7 @@ class CodeQLMCPClient { constructor(options = {}) { this.client = null; this.transport = null; + this.mcpMode = process.env.MCP_MODE || "stdio"; this.serverUrl = process.env.MCP_SERVER_URL || DEFAULT_SERVER_URL; this.timeout = parseInt(options.timeout || process.env.TIMEOUT_SECONDS || "30") * 1000; this.logger = new TestLogger(); @@ -113,14 +115,34 @@ class CodeQLMCPClient { */ async connect() { try { - this.logger.log(`Connecting to MCP server at ${this.serverUrl}`); + this.logger.log(`Connecting to MCP server (mode: ${this.mcpMode})`); this.client = new Client({ name: "codeql-development-mcp-client", version: "1.0.0" }); - this.transport = new StreamableHTTPClientTransport(new URL(this.serverUrl)); + if (this.mcpMode === "stdio") { + const serverPath = path.join( + __dirname, + "..", + "server", + "dist", + "codeql-development-mcp-server.js" + ); + this.transport = new StdioClientTransport({ + command: "node", + args: [serverPath], + env: { + ...process.env, + TRANSPORT_MODE: "stdio" + }, + stderr: "pipe" + }); + } else { + this.logger.log(`Server URL: ${this.serverUrl}`); + this.transport = new StreamableHTTPClientTransport(new URL(this.serverUrl)); + } // Set up timeout const connectPromise = this.client.connect(this.transport); @@ -169,7 +191,10 @@ class CodeQLMCPClient { */ async runTests() { this.logger.log("Starting CodeQL MCP Client Integration Tests"); - this.logger.log(`Server URL: ${this.serverUrl}`); + this.logger.log(`MCP Mode: ${this.mcpMode}`); + if (this.mcpMode === "http") { + this.logger.log(`Server URL: ${this.serverUrl}`); + } this.logger.log(`Timeout: ${this.timeout}ms`); // Check CodeQL CLI availability first @@ -212,7 +237,7 @@ class CodeQLMCPClient { */ async runMonitoringDemo() { this.logger.log("๐Ÿš€ Starting MCP Server Monitoring Demo"); - this.logger.log(`Server URL: ${this.serverUrl}`); + this.logger.log(`MCP Mode: ${this.mcpMode}`); let connected = false; @@ -315,7 +340,7 @@ class CodeQLMCPClient { */ async runWorkflowTests() { this.logger.log("๐Ÿ”„ Starting Workflow Integration Tests"); - this.logger.log(`Server URL: ${this.serverUrl}`); + this.logger.log(`MCP Mode: ${this.mcpMode}`); let connected = false; @@ -347,7 +372,7 @@ class CodeQLMCPClient { */ async runMonitoringIntegrationTests() { this.logger.log("๐Ÿ“Š Starting Monitoring Integration Tests"); - this.logger.log(`Server URL: ${this.serverUrl}`); + this.logger.log(`MCP Mode: ${this.mcpMode}`); let connected = false; diff --git a/docs/getting-started.md b/docs/getting-started.md index 4efb6b1..550f91b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -12,13 +12,13 @@ This guide covers installation, configuration, and usage of the CodeQL Developme ### VS Code Extension (recommended) -The easiest way to get started is the **VS Code extension**, which automates -installation, configuration, and CodeQL CLI discovery. -See the [VS Code Extension guide](./vscode/extension.md) for details. - -The `.vsix` can be downloaded from +The easiest and recommended way to get started is the **VS Code extension** (VSIX +archive bundle), which automates installation, configuration, and CodeQL CLI +discovery. Download the `.vsix` from [GitHub Releases](https://github.com/advanced-security/codeql-development-mcp-server/releases) -or built from source (`npm run package:vsix` at the repository root). +and install it in VS Code via `Extensions: Install from VSIXโ€ฆ` in the Command +Palette, or build from source (`npm run package:vsix` at the repository root). +See the [VS Code Extension guide](./vscode/extension.md) for details. ### From npm diff --git a/docs/public.md b/docs/public.md index 47830c6..ea7cb67 100644 --- a/docs/public.md +++ b/docs/public.md @@ -25,7 +25,22 @@ codeql --version # any recent release ## Installation -### 1. Install the MCP Server (npm) +### Recommended: VS Code Extension (VSIX archive bundle) + +The easiest and recommended way to get started is the **VS Code extension**, +distributed as a `.vsix` archive bundle. It automates installation, +configuration, and CodeQL CLI discovery โ€” no manual `mcp.json` editing required. + +1. Download the latest `.vsix` from + [GitHub Releases](https://github.com/advanced-security/codeql-development-mcp-server/releases). +2. In VS Code, open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and + run **Extensions: Install from VSIXโ€ฆ**. +3. Select the downloaded `.vsix` file. + +The extension bundles the MCP server and will register it automatically on +activation. See the [VS Code Extension guide](./vscode/extension.md) for details. + +### Alternative: Install the MCP Server (npm) The server is published as a public package on [npmjs.org](https://www.npmjs.com/package/codeql-development-mcp-server). No authentication or special configuration is needed: @@ -39,7 +54,7 @@ npx -y codeql-development-mcp-server The package ships the bundled server (`dist/codeql-development-mcp-server.js`), production dependencies, and the CodeQL tool query source packs (`ql/*/tools/src/`). -### 2. Install CodeQL Tool Query Packs (optional) +### Alternative: Install CodeQL Tool Query Packs (optional) The server ships with embedded copies of its tool query packs. If you prefer to manage the packs independently, or want to pin a specific version, download them from GHCR: diff --git a/docs/testing.md b/docs/testing.md index ba62ed2..a9c12d4 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -40,12 +40,14 @@ Unit tests verify the VS Code extension's TypeScript code outside of the Extensi Integration tests exercise individual MCP tools against a live server instance using the custom MCP client. -- **Client**: `client/src/ql-mcp-client.js` โ€” starts the MCP server, invokes tools, and validates results. +- **Client**: `client/src/ql-mcp-client.js` โ€” connects to the MCP server, invokes tools, and validates results. +- **Transport modes**: The client supports both `stdio` (default) and `http` transport modes, controlled by the `MCP_MODE` environment variable. In `stdio` mode the client spawns the server as a child process via `StdioClientTransport`; in `http` mode it connects to a separately started HTTP server via `StreamableHTTPClientTransport`. - **Test data**: `client/integration-tests/primitives/tools/` โ€” each test has `before/` and `after/` directories that define the initial fixture state and, for file-based tests, the expected final state. - **Run command**: `npm run test:integration:default -w client` (or `npm run test:client` from the repo root). - **Key properties**: - Tests are deterministic and repeatable. - No mocks โ€” tests use real CodeQL databases and queries bundled under `server/ql/`. + - The default transport is `stdio`, matching the primary user experience. - The `before/monitoring-state.json` file supplies tool arguments. For file-based tests, the integration-test runner diffs filesystem state from `before/` to `after/`; for monitoring-based tests, `after/` artifacts are generally not diffed and are only interpreted for specific validations (for example, `codeql_query_run` interpreted output). ### 2b โ€” Extension integration tests @@ -81,6 +83,7 @@ From the repository root: ```bash # Build everything and run all layers (1a + 1b + 2a + 2b) +# Integration tests use stdio transport by default npm run build-and-test # Run only server unit tests (1a) @@ -89,9 +92,13 @@ npm run test:server # Run extension unit tests + integration tests (1b + 2b) npm run test:vscode -# Run only MCP tool integration tests (2a) +# Run only MCP tool integration tests (2a) - stdio mode (default) npm run test:client +# Run MCP tool integration tests with explicit transport mode +MCP_MODE=stdio npm run test:client # stdio transport (default) +MCP_MODE=http npm run test:client # HTTP transport + # Run only extension integration tests (2b) npm run test:integration -w extensions/vscode ``` From 1802257df512b7744302f92f200189c4e4726c3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:24:24 +0000 Subject: [PATCH 3/6] feat: make stdio server path configurable via MCP_SERVER_PATH env var Add MCP_SERVER_PATH env var support for configuring the server binary path in stdio mode. Update CLI help text to document new environment variables. Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> --- client/src/lib/cli-parser.js | 6 ++++++ client/src/ql-mcp-client.js | 10 +++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/src/lib/cli-parser.js b/client/src/lib/cli-parser.js index 7b6573a..a80da46 100644 --- a/client/src/lib/cli-parser.js +++ b/client/src/lib/cli-parser.js @@ -133,6 +133,12 @@ OPTIONS: Example: --timeout 600 --help Display help information + +ENVIRONMENT VARIABLES: + MCP_MODE MCP transport mode: stdio (default) or http + MCP_SERVER_PATH Path to the MCP server JS entry point (stdio mode only) + MCP_SERVER_URL MCP server URL (http mode only, default: http://localhost:3000/mcp) + ENABLE_MONITORING_TOOLS Enable session_* monitoring tools (default: false) `; } diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index 74f70f2..0a907f7 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -123,13 +123,9 @@ class CodeQLMCPClient { }); if (this.mcpMode === "stdio") { - const serverPath = path.join( - __dirname, - "..", - "server", - "dist", - "codeql-development-mcp-server.js" - ); + const serverPath = + process.env.MCP_SERVER_PATH || + path.join(__dirname, "..", "server", "dist", "codeql-development-mcp-server.js"); this.transport = new StdioClientTransport({ command: "node", args: [serverPath], From 87e5248d2daafc6701174b2fdc49964402c20911 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Mon, 23 Feb 2026 11:00:39 -0700 Subject: [PATCH 4/6] Fixes for stdio-based client integration tests --- .gitignore | 3 +++ .../codeql_bqrs_interpret/sarif_format/after/results.sarif | 2 +- client/src/ql-mcp-client.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index bbc674b..c2b3b50 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,9 @@ codeql-development-mcp-server.code-workspace # Prevent accidentally committing integration test output files in root directory # These should only be in client/integration-tests/primitives/tools/*/after/ directories /evaluator-log.json +/client/query-results/ +/client/query-results.bqrs +/client/query-results.sarif /query-results/ /query-results.bqrs /query-results.sarif diff --git a/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif b/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif index ff3d8fe..22727fc 100644 --- a/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif +++ b/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif @@ -1 +1 @@ -{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"CodeQL","organization":"GitHub","semanticVersion":"2.24.2","rules":[{"id":"test/query","name":"test/query","shortDescription":{"text":"ExampleQuery1"},"fullDescription":{"text":"Example query for integration testing of the codeql_test_extract MCP server tool."},"defaultConfiguration":{"enabled":true,"level":"warning"},"help":{"text":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n","markdown":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n"},"properties":{"tags":["mcp-integration-tests"],"description":"Example query for integration testing of the codeql_test_extract MCP server tool.","id":"test/query","kind":"problem","name":"ExampleQuery1","precision":"medium","problem.severity":"warning"}}]},"extensions":[{"name":"mcp-client-integration-tests-static-javascript-src","semanticVersion":"0.0.1+fe0e7d2a7059ebb6c6075ff8eaea04f382747656","locations":[{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/codeql-pack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/javascript-all","semanticVersion":"2.6.11+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/threat-models","semanticVersion":"1.0.31+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]}]},"artifacts":[{"location":{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}],"results":[{"ruleId":"test/query","ruleIndex":0,"rule":{"id":"test/query","index":0},"message":{"text":"Example test code file found for codeql_test_extract example query."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}}]}],"columnKind":"utf16CodeUnits","properties":{"semmle.formatSpecifier":"sarif-latest"}}]} \ No newline at end of file +{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"CodeQL","organization":"GitHub","semanticVersion":"2.24.1","rules":[{"id":"test/query","name":"test/query","shortDescription":{"text":"ExampleQuery1"},"fullDescription":{"text":"Example query for integration testing of the codeql_test_extract MCP server tool."},"defaultConfiguration":{"enabled":true,"level":"warning"},"help":{"text":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n","markdown":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n"},"properties":{"tags":["mcp-integration-tests"],"description":"Example query for integration testing of the codeql_test_extract MCP server tool.","id":"test/query","kind":"problem","name":"ExampleQuery1","precision":"medium","problem.severity":"warning"}}]},"extensions":[{"name":"mcp-client-integration-tests-static-javascript-src","semanticVersion":"0.0.1+fe0e7d2a7059ebb6c6075ff8eaea04f382747656","locations":[{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/codeql-pack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/javascript-all","semanticVersion":"2.6.11+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/threat-models","semanticVersion":"1.0.31+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]}]},"artifacts":[{"location":{"uri":"file:/home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}],"results":[{"ruleId":"test/query","ruleIndex":0,"rule":{"id":"test/query","index":0},"message":{"text":"Example test code file found for codeql_test_extract example query."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:/home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}}]}],"columnKind":"utf16CodeUnits","properties":{"semmle.formatSpecifier":"sarif-latest"}}]} \ No newline at end of file diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index 0a907f7..85eebd8 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -125,7 +125,7 @@ class CodeQLMCPClient { if (this.mcpMode === "stdio") { const serverPath = process.env.MCP_SERVER_PATH || - path.join(__dirname, "..", "server", "dist", "codeql-development-mcp-server.js"); + path.join(__dirname, "..", "..", "server", "dist", "codeql-development-mcp-server.js"); this.transport = new StdioClientTransport({ command: "node", args: [serverPath], From c4ce20c97cfbbd65181c0b2db5ffc571f045d6e8 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Mon, 23 Feb 2026 11:51:38 -0700 Subject: [PATCH 5/6] fix bugs in stdio MCP client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 โ€” CWD mismatch in stdio mode (both platforms): StdioClientTransport inherited the client/ working directory, so relative paths passed to CodeQL CLI (e.g. server/ql/javascript/...) resolved to client/server/ql/... which does not exist. Add cwd: repoRoot to the transport options to match the HTTP-mode behavior where start-server.sh explicitly cd's to the repo root. Bug 2 โ€” False success on Windows in stdio mode: StdioClientTransport.close() could cause an abrupt process exit on Windows before printTestSummary() and process.exit(exitCode) were reached, so the shell script saw exit code 0 and reported success despite test failures. Move summary printing and process.exitCode assignment above the disconnect() call in all four run methods so results are always reported even if disconnect triggers an early exit. --- .../sarif_format/after/results.sarif | 2 +- client/src/ql-mcp-client.js | 88 +++++++++++++------ 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif b/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif index 22727fc..ff3d8fe 100644 --- a/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif +++ b/client/integration-tests/primitives/tools/codeql_bqrs_interpret/sarif_format/after/results.sarif @@ -1 +1 @@ -{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"CodeQL","organization":"GitHub","semanticVersion":"2.24.1","rules":[{"id":"test/query","name":"test/query","shortDescription":{"text":"ExampleQuery1"},"fullDescription":{"text":"Example query for integration testing of the codeql_test_extract MCP server tool."},"defaultConfiguration":{"enabled":true,"level":"warning"},"help":{"text":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n","markdown":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n"},"properties":{"tags":["mcp-integration-tests"],"description":"Example query for integration testing of the codeql_test_extract MCP server tool.","id":"test/query","kind":"problem","name":"ExampleQuery1","precision":"medium","problem.severity":"warning"}}]},"extensions":[{"name":"mcp-client-integration-tests-static-javascript-src","semanticVersion":"0.0.1+fe0e7d2a7059ebb6c6075ff8eaea04f382747656","locations":[{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/codeql-pack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/javascript-all","semanticVersion":"2.6.11+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/threat-models","semanticVersion":"1.0.31+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]}]},"artifacts":[{"location":{"uri":"file:/home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}],"results":[{"ruleId":"test/query","ruleIndex":0,"rule":{"id":"test/query","index":0},"message":{"text":"Example test code file found for codeql_test_extract example query."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:/home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}}]}],"columnKind":"utf16CodeUnits","properties":{"semmle.formatSpecifier":"sarif-latest"}}]} \ No newline at end of file +{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"CodeQL","organization":"GitHub","semanticVersion":"2.24.2","rules":[{"id":"test/query","name":"test/query","shortDescription":{"text":"ExampleQuery1"},"fullDescription":{"text":"Example query for integration testing of the codeql_test_extract MCP server tool."},"defaultConfiguration":{"enabled":true,"level":"warning"},"help":{"text":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n","markdown":"# Query Help for JavaScript ExampleQuery1\n\nTODO\n"},"properties":{"tags":["mcp-integration-tests"],"description":"Example query for integration testing of the codeql_test_extract MCP server tool.","id":"test/query","kind":"problem","name":"ExampleQuery1","precision":"medium","problem.severity":"warning"}}]},"extensions":[{"name":"mcp-client-integration-tests-static-javascript-src","semanticVersion":"0.0.1+fe0e7d2a7059ebb6c6075ff8eaea04f382747656","locations":[{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/src/codeql-pack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/javascript-all","semanticVersion":"2.6.11+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/javascript-all/2.6.11/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]},{"name":"codeql/threat-models","semanticVersion":"1.0.31+ce9c8e6e9fd41ef0967b13849bb6ae2183caf9ad","locations":[{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/","description":{"text":"The QL pack root directory."},"properties":{"tags":["CodeQL/LocalPackRoot"]}},{"uri":"file:///home/runner/.codeql/packages/codeql/threat-models/1.0.31/qlpack.yml","description":{"text":"The QL pack definition file."},"properties":{"tags":["CodeQL/LocalPackDefinitionFile"]}}]}]},"artifacts":[{"location":{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}],"results":[{"ruleId":"test/query","ruleIndex":0,"rule":{"id":"test/query","index":0},"message":{"text":"Example test code file found for codeql_test_extract example query."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/codeql-development-mcp-server/codeql-development-mcp-server/client/integration-tests/static/javascript/test/ExampleQuery1/ExampleQuery1.js","index":0}}}]}],"columnKind":"utf16CodeUnits","properties":{"semmle.formatSpecifier":"sarif-latest"}}]} \ No newline at end of file diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index 85eebd8..ef7f68e 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -123,12 +123,14 @@ class CodeQLMCPClient { }); if (this.mcpMode === "stdio") { + const repoRoot = path.join(__dirname, "..", ".."); const serverPath = process.env.MCP_SERVER_PATH || - path.join(__dirname, "..", "..", "server", "dist", "codeql-development-mcp-server.js"); + path.join(repoRoot, "server", "dist", "codeql-development-mcp-server.js"); this.transport = new StdioClientTransport({ command: "node", args: [serverPath], + cwd: repoRoot, env: { ...process.env, TRANSPORT_MODE: "stdio" @@ -215,17 +217,24 @@ class CodeQLMCPClient { } } catch (error) { this.logger.log(`Test execution failed: ${error.message}`, "ERROR"); - } finally { - if (connected) { - await this.disconnect(); - } } - // Print test summary + // Print test summary and set exit code BEFORE disconnect. + // On Windows, StdioClientTransport.close() can cause the Node.js + // process to exit abruptly, so we must report results first. this.logger.printTestSummary(); + const exitCode = this.logger.isSuccess() ? 0 : 1; + process.exitCode = exitCode; + + if (connected) { + try { + await this.disconnect(); + } catch { + // Ignore disconnect errors โ€” results are already reported + } + } - // Exit with appropriate code - process.exit(this.logger.isSuccess() ? 0 : 1); + process.exit(exitCode); } /** @@ -246,17 +255,24 @@ class CodeQLMCPClient { } } catch (error) { this.logger.log(`Demo execution failed: ${error.message}`, "ERROR"); - } finally { - if (connected) { - await this.disconnect(); - } } - // Print demo summary + // Print summary and set exit code BEFORE disconnect. + // On Windows, StdioClientTransport.close() can cause the Node.js + // process to exit abruptly, so we must report results first. this.logger.printTestSummary(); + const exitCode = this.logger.isSuccess() ? 0 : 1; + process.exitCode = exitCode; + + if (connected) { + try { + await this.disconnect(); + } catch { + // Ignore disconnect errors โ€” results are already reported + } + } - // Exit with appropriate code - process.exit(this.logger.isSuccess() ? 0 : 1); + process.exit(exitCode); } /** @@ -350,17 +366,24 @@ class CodeQLMCPClient { } } catch (error) { this.logger.log(`Workflow test execution failed: ${error.message}`, "ERROR"); - } finally { - if (connected) { - await this.disconnect(); - } } - // Print test summary + // Print test summary and set exit code BEFORE disconnect. + // On Windows, StdioClientTransport.close() can cause the Node.js + // process to exit abruptly, so we must report results first. this.logger.printTestSummary(); + const exitCode = this.logger.isSuccess() ? 0 : 1; + process.exitCode = exitCode; + + if (connected) { + try { + await this.disconnect(); + } catch { + // Ignore disconnect errors โ€” results are already reported + } + } - // Exit with appropriate code - process.exit(this.logger.isSuccess() ? 0 : 1); + process.exit(exitCode); } /** @@ -387,17 +410,24 @@ class CodeQLMCPClient { } } catch (error) { this.logger.log(`Monitoring test execution failed: ${error.message}`, "ERROR"); - } finally { - if (connected) { - await this.disconnect(); - } } - // Print test summary + // Print test summary and set exit code BEFORE disconnect. + // On Windows, StdioClientTransport.close() can cause the Node.js + // process to exit abruptly, so we must report results first. this.logger.printTestSummary(); + const exitCode = this.logger.isSuccess() ? 0 : 1; + process.exitCode = exitCode; + + if (connected) { + try { + await this.disconnect(); + } catch { + // Ignore disconnect errors โ€” results are already reported + } + } - // Exit with appropriate code - process.exit(this.logger.isSuccess() ? 0 : 1); + process.exit(exitCode); } /** From 04661fbd389cf484e07569afd9be5a37c92bd284 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:46:57 +0000 Subject: [PATCH 6/6] fix: address review feedback on transport selection and shell arg forwarding - Normalize MCP_MODE to lowercase in client constructor so that MCP_MODE=STDIO works the same as MCP_MODE=stdio - Select stdio transport by default; only use HTTP when MCP_MODE === "http" (previously any non-"stdio" value fell through to HTTP) - Add shift 2 in run_tests_in_mode() so mode_name and enable_monitoring positional args are not forwarded to node as extra CLI arguments Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com> --- client/scripts/run-integration-tests.sh | 1 + client/src/ql-mcp-client.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/scripts/run-integration-tests.sh b/client/scripts/run-integration-tests.sh index a10e38f..49aa18d 100755 --- a/client/scripts/run-integration-tests.sh +++ b/client/scripts/run-integration-tests.sh @@ -97,6 +97,7 @@ fi run_tests_in_mode() { local mode_name="$1" local enable_monitoring="$2" + shift 2 echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" diff --git a/client/src/ql-mcp-client.js b/client/src/ql-mcp-client.js index ef7f68e..9d20844 100755 --- a/client/src/ql-mcp-client.js +++ b/client/src/ql-mcp-client.js @@ -37,7 +37,7 @@ class CodeQLMCPClient { constructor(options = {}) { this.client = null; this.transport = null; - this.mcpMode = process.env.MCP_MODE || "stdio"; + this.mcpMode = (process.env.MCP_MODE || "stdio").toLowerCase(); this.serverUrl = process.env.MCP_SERVER_URL || DEFAULT_SERVER_URL; this.timeout = parseInt(options.timeout || process.env.TIMEOUT_SECONDS || "30") * 1000; this.logger = new TestLogger(); @@ -122,7 +122,7 @@ class CodeQLMCPClient { version: "1.0.0" }); - if (this.mcpMode === "stdio") { + if (this.mcpMode !== "http") { const repoRoot = path.join(__dirname, "..", ".."); const serverPath = process.env.MCP_SERVER_PATH ||