Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion scripts/mcp-smoke.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 || '',
Expand Down
8 changes: 8 additions & 0 deletions src/mcp/licensing/licenseValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
* 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 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';
Expand All @@ -33,7 +37,7 @@ export function createProvarMcpServer(config: ServerConfig): McpServer {

const server = new McpServer({
name: 'provar-mcp',
version: '1.0.0',
version: SERVER_VERSION,
});

// ── Sanity-check tool ────────────────────────────────────────────────────────
Expand All @@ -44,7 +48,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,
Expand Down
16 changes: 1 addition & 15 deletions src/mcp/tools/automationTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ──────────────────────────────────────────────────────────

Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/mcp/tools/qualityHubTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,17 @@ 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);
const failureStatuses = new Set(['FAIL', 'FAILED']);
let hasFailures = false;
try {
const parsed = JSON.parse(result.stdout) as { result?: { status?: string } };
const normalizedStatus = parsed.result?.status?.trim().toUpperCase();
hasFailures = normalizedStatus !== undefined && failureStatuses.has(normalizedStatus);
} catch {
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).'
: '';
Expand Down
10 changes: 8 additions & 2 deletions src/mcp/tools/sfSpawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
Loading