From 33923f0a685135de568afbf9c8938b2189520f08 Mon Sep 17 00:00:00 2001 From: Michael Dailey Date: Tue, 7 Apr 2026 09:47:38 -0500 Subject: [PATCH 1/3] fix: address deferred MCP review items 11-16 - server.ts: read version from package.json via createRequire instead of hardcoding '1.0.0'; applies to both McpServer constructor and ping tool response - sfSpawn.ts: update SfNotFoundError to accept optional sfPath with richer diagnostic message (includes explicit path or 'common npm/nvm/volta locations') - automationTools.ts: remove duplicate SfNotFoundError class; import from sfSpawn.ts - mcp-smoke.cjs: replace shell:true with shell:process.platform==='win32' to avoid unnecessary shell invocation on Linux/macOS CI - qualityHubTools.ts: replace broad /fail/i stdout scan with JSON-parse of result.status; falls back to word-boundary /\bFAILED?\b/i on parse failure - licenseValidator.ts: add warning log when IDE's lastOnlineCheckMs is > 7 days old so stale activations are surfaced before causing auth issues - package.json: remove 'yarn' from devDependencies (yarn is a package manager, not an npm package dep; GitHub Actions runners provide it via system install) Co-Authored-By: Claude Sonnet 4.6 --- package.json | 1 - scripts/mcp-smoke.cjs | 2 +- src/mcp/licensing/licenseValidator.ts | 8 ++++++++ src/mcp/server.ts | 9 +++++++-- src/mcp/tools/automationTools.ts | 16 +--------------- src/mcp/tools/qualityHubTools.ts | 8 +++++++- src/mcp/tools/sfSpawn.ts | 10 ++++++++-- 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 9978436f..28b5a8ec 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.0.2", - "yarn": "^1.22.22", "@salesforce/cli-plugins-testkit": "^5.1.7", "@salesforce/dev-scripts": "^8.3.0", "@types/sinon": "^21.0.0", diff --git a/scripts/mcp-smoke.cjs b/scripts/mcp-smoke.cjs index 670bb498..952f283f 100644 --- a/scripts/mcp-smoke.cjs +++ b/scripts/mcp-smoke.cjs @@ -27,7 +27,7 @@ const INCLUDE_SETUP = process.env['SMOKE_INCLUDE_SETUP'] === '1'; // ---------------------------------------------------------------------------- const server = spawn('sf', ['provar', 'mcp', 'start', '--allowed-paths', TMP], { stdio: ['pipe', 'pipe', 'inherit'], - shell: true, + shell: process.platform === 'win32', env: { ...process.env, PROVAR_DEV_WHITELIST_KEYS: process.env.PROVAR_DEV_WHITELIST_KEYS || '', diff --git a/src/mcp/licensing/licenseValidator.ts b/src/mcp/licensing/licenseValidator.ts index 421a9c76..2634e2a8 100644 --- a/src/mcp/licensing/licenseValidator.ts +++ b/src/mcp/licensing/licenseValidator.ts @@ -111,6 +111,14 @@ function validateViaIdeDetection(): LicenseValidationResult { ); } + // Warn when the IDE's own ALGAS check is stale (> 7 days) — the IDE may not have been + // opened recently enough to refresh the activation status from the licensing server. + const IDE_FRESHNESS_WARN_MS = 7 * 24 * 60 * 60 * 1000; + if (ideState.lastOnlineCheckMs > 0 && Date.now() - ideState.lastOnlineCheckMs > IDE_FRESHNESS_WARN_MS) { + const ageDays = Math.round((Date.now() - ideState.lastOnlineCheckMs) / (24 * 60 * 60 * 1000)); + log('warn', 'licenseValidator: IDE license online check is stale — open Provar IDE to refresh', { ageDays }); + } + // 3. Valid — write to MCP cache so next start within 2h skips this read const entry: CacheEntry = { keyHash, diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 3762140b..1aa0f20d 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -5,9 +5,14 @@ * For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { createRequire } from 'node:module'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { log } from './logging/logger.js'; + +const _require = createRequire(import.meta.url); +const _pkg = _require('../../package.json') as { version: string }; +const SERVER_VERSION: string = _pkg.version; import { registerProjectInspect } from './tools/projectInspect.js'; import { registerPageObjectGenerate } from './tools/pageObjectGenerate.js'; import { registerPageObjectValidate } from './tools/pageObjectValidate.js'; @@ -33,7 +38,7 @@ export function createProvarMcpServer(config: ServerConfig): McpServer { const server = new McpServer({ name: 'provar-mcp', - version: '1.0.0', + version: SERVER_VERSION, }); // ── Sanity-check tool ──────────────────────────────────────────────────────── @@ -44,7 +49,7 @@ export function createProvarMcpServer(config: ServerConfig): McpServer { message: z.string().optional().default('ping').describe('Optional message to echo back'), }, ({ message }) => { - const result = { pong: message, ts: new Date().toISOString(), server: 'provar-mcp@1.0.0' }; + const result = { pong: message, ts: new Date().toISOString(), server: `provar-mcp@${SERVER_VERSION}` }; return { content: [{ type: 'text' as const, text: JSON.stringify(result) }], structuredContent: result, diff --git a/src/mcp/tools/automationTools.ts b/src/mcp/tools/automationTools.ts index fefdca8e..130b1c49 100644 --- a/src/mcp/tools/automationTools.ts +++ b/src/mcp/tools/automationTools.ts @@ -15,7 +15,7 @@ import { makeError, makeRequestId } from '../schemas/common.js'; import { log } from '../logging/logger.js'; import type { ServerConfig } from '../server.js'; import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js'; -import { sfSpawnHelper } from './sfSpawn.js'; +import { sfSpawnHelper, SfNotFoundError } from './sfSpawn.js'; // ── SF CLI discovery ────────────────────────────────────────────────────────── @@ -89,20 +89,6 @@ function resolveSfExecutable(): string | null { return null; } -class SfNotFoundError extends Error { - public readonly code = 'SF_NOT_FOUND'; - public constructor(sfPath?: string) { - const where = sfPath - ? `at explicit path "${sfPath}"` - : 'in PATH or common npm/nvm install locations'; - super( - `sf CLI not found ${where}. ` + - 'Install Salesforce CLI (npm install -g @salesforce/cli) and ensure the install directory is in your PATH, ' + - 'or pass sf_path pointing to the sf executable directly ' + - '(e.g. "~/.nvm/versions/node/v22.0.0/bin/sf").' - ); - } -} function runSfCommand(args: string[], sfPath?: string): SpawnResult { // Use explicit path if provided; otherwise use cached probe result diff --git a/src/mcp/tools/qualityHubTools.ts b/src/mcp/tools/qualityHubTools.ts index 5162af24..aee6a5e7 100644 --- a/src/mcp/tools/qualityHubTools.ts +++ b/src/mcp/tools/qualityHubTools.ts @@ -143,7 +143,13 @@ export function registerQualityHubTestRunReport(server: McpServer): void { return { isError: true as const, content: [{ type: 'text' as const, text: JSON.stringify(makeError('QH_REPORT_FAILED', result.stderr || result.stdout, requestId)) }] }; } - const hasFailures = /fail/i.test(result.stdout); + let hasFailures = false; + try { + const parsed = JSON.parse(result.stdout) as { result?: { status?: string } }; + hasFailures = /fail/i.test(parsed.result?.status ?? ''); + } catch { + hasFailures = /\bFAILED?\b/i.test(result.stdout); + } const suggestion = hasFailures ? 'Failures detected. Use provar.qualityhub.defect.create with run_id and target_org to automatically create Defect__c records for each failure (syncs to Jira/ADO if configured).' : ''; diff --git a/src/mcp/tools/sfSpawn.ts b/src/mcp/tools/sfSpawn.ts index 47417af3..5b7fd3c3 100644 --- a/src/mcp/tools/sfSpawn.ts +++ b/src/mcp/tools/sfSpawn.ts @@ -19,9 +19,15 @@ export const sfSpawnHelper = { export class SfNotFoundError extends Error { public readonly code = 'SF_NOT_FOUND'; - public constructor() { + public constructor(sfPath?: string) { + const where = sfPath + ? `at explicit path "${sfPath}"` + : 'in PATH or common npm/nvm/volta install locations'; super( - 'sf CLI not found in PATH. Install Salesforce CLI (`npm install -g @salesforce/cli`) and ensure it is in your PATH.' + `sf CLI not found ${where}. ` + + 'Install Salesforce CLI (npm install -g @salesforce/cli) and ensure the install directory is in your PATH, ' + + 'or pass sf_path pointing to the sf executable directly ' + + '(e.g. "~/.nvm/versions/node/v22.0.0/bin/sf").' ); this.name = 'SfNotFoundError'; } From a2797a7f89d824c70d17953617396953f826ddf7 Mon Sep 17 00:00:00 2001 From: Michael Dailey Date: Tue, 7 Apr 2026 09:52:11 -0500 Subject: [PATCH 2/3] fix: remove underscore-prefixed vars in server.ts (no-underscore-dangle) --- src/mcp/server.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 1aa0f20d..a81320d7 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -10,9 +10,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { log } from './logging/logger.js'; -const _require = createRequire(import.meta.url); -const _pkg = _require('../../package.json') as { version: string }; -const SERVER_VERSION: string = _pkg.version; +const requireJson = createRequire(import.meta.url); +const SERVER_VERSION: string = (requireJson('../../package.json') as { version: string }).version; import { registerProjectInspect } from './tools/projectInspect.js'; import { registerPageObjectGenerate } from './tools/pageObjectGenerate.js'; import { registerPageObjectValidate } from './tools/pageObjectValidate.js'; From d91a32dffd6ea07feb08cc978eb1150d1b753276 Mon Sep 17 00:00:00 2001 From: Michael Dailey <49916244+mrdailey99@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:06:28 -0500 Subject: [PATCH 3/3] Update src/mcp/tools/qualityHubTools.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mcp/tools/qualityHubTools.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mcp/tools/qualityHubTools.ts b/src/mcp/tools/qualityHubTools.ts index aee6a5e7..223dc7d0 100644 --- a/src/mcp/tools/qualityHubTools.ts +++ b/src/mcp/tools/qualityHubTools.ts @@ -143,12 +143,16 @@ export function registerQualityHubTestRunReport(server: McpServer): void { return { isError: true as const, content: [{ type: 'text' as const, text: JSON.stringify(makeError('QH_REPORT_FAILED', result.stderr || result.stdout, requestId)) }] }; } + const failureStatuses = new Set(['FAIL', 'FAILED']); let hasFailures = false; try { const parsed = JSON.parse(result.stdout) as { result?: { status?: string } }; - hasFailures = /fail/i.test(parsed.result?.status ?? ''); + const normalizedStatus = parsed.result?.status?.trim().toUpperCase(); + hasFailures = normalizedStatus !== undefined && failureStatuses.has(normalizedStatus); } catch { - hasFailures = /\bFAILED?\b/i.test(result.stdout); + const statusMatch = result.stdout.match(/"status"\s*:\s*"([^"]+)"/i); + const normalizedStatus = statusMatch?.[1]?.trim().toUpperCase(); + hasFailures = normalizedStatus !== undefined && failureStatuses.has(normalizedStatus); } const suggestion = hasFailures ? 'Failures detected. Use provar.qualityhub.defect.create with run_id and target_org to automatically create Defect__c records for each failure (syncs to Jira/ADO if configured).'