Skip to content

Commit b9e2310

Browse files
Copilotdata-douser
andcommitted
fix: cross-platform support for Windows compatibility
- Use pathToFileURL() instead of file:// string concatenation in: - server/src/ql-mcp-server.ts (entrypoint check) - server/src/tools/codeql/language-server-eval.ts (workspace URI) - client/src/ql-mcp-client.js (entrypoint check) - Fix path.includes() to normalize separators in cli-tool-registry.ts - Replace .split('/').pop() with path.basename() for cross-platform path handling - Update client-integration-tests.yml to run on matrix of ubuntu-latest and windows-latest - Add platform-specific CI steps for process cleanup and OS dependencies - Update setup-codeql-environment action with Windows cache paths Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
1 parent bc5fdd5 commit b9e2310

9 files changed

Lines changed: 99 additions & 33 deletions

File tree

.github/actions/setup-codeql-environment/action.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ runs:
7575
echo " CodeQL Version: $CODEQL_VERSION"
7676
echo " Cache Key: $CODEQL_CACHE_KEY"
7777
78-
- name: Cache `gh-codeql` extension and CodeQL packages
79-
id: cache-codeql
78+
- name: Cache `gh-codeql` extension and CodeQL packages (Unix)
79+
id: cache-codeql-unix
80+
if: runner.os != 'Windows'
8081
uses: actions/cache@v4
8182
with:
8283
path: |
@@ -86,6 +87,18 @@ runs:
8687
restore-keys: |
8788
gh-codeql-${{ runner.os }}-${{ steps.codeql-version.outputs.codeql-version }}-
8889
90+
- name: Cache `gh-codeql` extension and CodeQL packages (Windows)
91+
id: cache-codeql-windows
92+
if: runner.os == 'Windows'
93+
uses: actions/cache@v4
94+
with:
95+
path: |
96+
~\AppData\Local\GitHub\gh-codeql
97+
~\.codeql\packages
98+
key: ${{ steps.codeql-version.outputs.codeql-cache-key }}
99+
restore-keys: |
100+
gh-codeql-${{ runner.os }}-${{ steps.codeql-version.outputs.codeql-version }}-
101+
89102
# Install GitHub CLI CodeQL extension and set `codeql` CLI version
90103
- name: Install GitHub CLI CodeQL extension and set version
91104
id: install-gh-codeql

.github/workflows/client-integration-tests.yml

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ permissions:
2626

2727
jobs:
2828
integration-tests:
29-
runs-on: ubuntu-latest
29+
name: Integration Tests (${{ matrix.os }})
30+
runs-on: ${{ matrix.os }}
31+
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
os: [ubuntu-latest, windows-latest]
3036

3137
env:
3238
HTTP_HOST: 'localhost'
@@ -44,9 +50,14 @@ jobs:
4450
cache: 'npm'
4551
node-version-file: '.node-version'
4652

47-
- name: MCP Integration Tests - Install OS dependencies
53+
- name: MCP Integration Tests - Install OS dependencies (Ubuntu)
54+
if: runner.os == 'Linux'
4855
run: sudo apt-get install -y jq
4956

57+
- name: MCP Integration Tests - Install OS dependencies (Windows)
58+
if: runner.os == 'Windows'
59+
run: choco install jq -y
60+
5061
- name: MCP Integration Tests - Install node dependencies for client and server workspaces
5162
run: npm ci --workspace=client && npm ci --workspace=server
5263

@@ -57,10 +68,12 @@ jobs:
5768

5869
## Install packs used in the integration tests.
5970
- name: MCP Integration Tests - Install CodeQL packs
71+
shell: bash
6072
run: ./server/scripts/install-packs.sh
6173

6274
## Extract test databases used in the integration tests.
6375
- name: MCP Integration Tests - Extract test databases
76+
shell: bash
6477
run: ./server/scripts/extract-test-databases.sh
6578

6679
## Run integration tests. This script builds the server bundle and runs tests.
@@ -69,8 +82,8 @@ jobs:
6982
- name: MCP Integration Tests - Run integration tests
7083
run: npm run test:integration --workspace=client
7184

72-
- name: MCP Integration Tests - Stop the background MCP server process
73-
if: always()
85+
- name: MCP Integration Tests - Stop the background MCP server process (Unix)
86+
if: always() && runner.os != 'Windows'
7487
run: |
7588
if [ -f server.pid ]; then
7689
PID=$(cat server.pid)
@@ -96,3 +109,38 @@ jobs:
96109
echo "Removing server.log"
97110
rm server.log
98111
fi
112+
113+
- name: MCP Integration Tests - Stop the background MCP server process (Windows)
114+
if: always() && runner.os == 'Windows'
115+
shell: pwsh
116+
run: |
117+
if (Test-Path server.pid) {
118+
$PID = Get-Content server.pid
119+
Write-Host "Stopping server with PID $PID"
120+
try {
121+
$process = Get-Process -Id $PID -ErrorAction SilentlyContinue
122+
if ($process) {
123+
Stop-Process -Id $PID -Force -ErrorAction SilentlyContinue
124+
Start-Sleep -Seconds 2
125+
} else {
126+
Write-Host "Server process was not running"
127+
}
128+
} catch {
129+
Write-Host "Server process was not running or already stopped"
130+
}
131+
Remove-Item server.pid -Force -ErrorAction SilentlyContinue
132+
} else {
133+
Write-Host "No server.pid file found"
134+
}
135+
136+
# Clean up log files
137+
if (Test-Path server.log) {
138+
Write-Host "Removing server.log"
139+
Remove-Item server.log -Force -ErrorAction SilentlyContinue
140+
}
141+
142+
- name: MCP Integration Tests - Summary
143+
shell: bash
144+
run: |
145+
echo "## Integration Tests Summary (${{ matrix.os }})" >> $GITHUB_STEP_SUMMARY
146+
echo "✅ MCP server integration tests passed on ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY

client/src/ql-mcp-client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1111
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1212
import { execSync } from "child_process";
1313
import dotenv from "dotenv";
14-
import { fileURLToPath } from "url";
14+
import { fileURLToPath, pathToFileURL } from "url";
1515
import path from "path";
1616

1717
import { IntegrationTestRunner } from "./lib/integration-test-runner.js";
@@ -807,7 +807,7 @@ async function main() {
807807
}
808808

809809
// Run if called directly
810-
if (import.meta.url === `file://${process.argv[1]}`) {
810+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
811811
main().catch((error) => {
812812
console.error("Fatal error:", error);
813813
process.exit(1);

server/dist/ql-mcp-server.js

Lines changed: 13 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/dist/ql-mcp-server.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/src/lib/cli-tool-registry.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ import { logger } from '../utils/logger';
99
import { evaluateQueryResults, QueryEvaluationResult, extractQueryMetadata } from './query-results-evaluator';
1010
import { getOrCreateLogDirectory } from './log-directory-manager';
1111
import { writeFileSync, rmSync, existsSync, mkdirSync } from 'fs';
12-
import { join, dirname, resolve } from 'path';
12+
import { join, dirname, resolve, basename } from 'path';
1313
import { createProjectTempDir } from '../utils/temp-dir';
1414
import { fileURLToPath } from 'url';
1515

1616
const __filename = fileURLToPath(import.meta.url);
1717
const __dirname = dirname(__filename);
1818
// Calculate the repository root directory
1919
// When running from source: server/src/lib/ -> go up 3 levels to repo root
20-
// When running from bundle: server/dist/ -> go up 2 levels to repo root
20+
// When running from bundle: server/dist/ -> go up 2 levels to repo root
2121
// The bundled file flattens the structure, so we detect based on path
22-
const repoRootDir = __dirname.includes('src/lib')
22+
// Normalize path separators for cross-platform compatibility (Windows uses '\', Unix uses '/')
23+
const normalizedDir = __dirname.split(/[\\/]/).join('/');
24+
const repoRootDir = normalizedDir.includes('src/lib')
2325
? resolve(__dirname, '..', '..', '..') // From source: server/src/lib -> repo root
2426
: resolve(__dirname, '..', '..'); // From bundle: server/dist -> repo root
2527

@@ -637,13 +639,13 @@ async function resolveQueryPath(
637639

638640
// Find the query that matches the requested name exactly
639641
const matchingQuery = resolvedQueries.find(queryPath => {
640-
const fileName = queryPath.split('/').pop();
642+
const fileName = basename(queryPath);
641643
// Match exact query name: "PrintAST" should match "PrintAST.ql" only
642644
return fileName === `${queryName}.ql`;
643645
});
644-
646+
645647
if (!matchingQuery) {
646-
logger.error(`Query "${queryName}.ql" not found in pack "${packPath}". Available queries:`, resolvedQueries.map(q => q.split('/').pop()));
648+
logger.error(`Query "${queryName}.ql" not found in pack "${packPath}". Available queries:`, resolvedQueries.map(q => basename(q)));
647649
throw new Error(`Query "${queryName}.ql" not found in pack "${packPath}"`);
648650
}
649651

server/src/prompts/workflow-prompts.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
99
import { z } from 'zod';
10+
import { basename } from 'path';
1011
import { loadPromptTemplate, processPromptTemplate } from './prompt-loader';
1112
import { logger } from '../utils/logger';
1213

@@ -154,10 +155,8 @@ export function registerWorkflowPrompts(server: McpServer): void {
154155
// Derive workshop name from query path if not provided
155156
const derivedName =
156157
workshopName ||
157-
queryPath
158-
.split('/')
159-
.pop()
160-
?.replace(/\.(ql|qlref)$/, '')
158+
basename(queryPath)
159+
.replace(/\.(ql|qlref)$/, '')
161160
.toLowerCase()
162161
.replace(/[^a-z0-9]+/g, '-') ||
163162
'codeql-workshop';

server/src/ql-mcp-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
99
import express from 'express';
1010
import cors from 'cors';
1111
import dotenv from 'dotenv';
12+
import { pathToFileURL } from 'url';
1213
import { registerCodeQLTools, registerCodeQLResources } from './tools';
1314
import { registerLanguageResources } from './resources/language-resources';
1415
import { registerWorkflowPrompts } from './prompts/workflow-prompts';
@@ -140,6 +141,6 @@ async function main(): Promise<void> {
140141
}
141142

142143
// Start the server if this file is run directly
143-
if (import.meta.url === `file://${process.argv[1]}`) {
144+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
144145
main();
145146
}

server/src/tools/codeql/language-server-eval.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ async function getLanguageServer(options: LanguageServerOptions = {}): Promise<C
103103
await globalLanguageServer.start();
104104

105105
// Use provided workspace URI or default to ql directory
106-
const workspaceUri = `file://${resolve(process.cwd(), 'ql')}`;
106+
const workspaceUri = pathToFileURL(resolve(process.cwd(), 'ql')).href;
107107
await globalLanguageServer.initialize(workspaceUri);
108108

109109
logger.info('CodeQL Language Server started and initialized successfully');

0 commit comments

Comments
 (0)