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
13 changes: 7 additions & 6 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -2038,13 +2038,14 @@ Read cached Salesforce describe data for one connection from the Provar workspac

**Prerequisite:** the project must have been opened in Provar IDE at least once with the named connection loaded, and (for the chosen test environment) the relevant objects expanded so their metadata is written to disk. If the cache is missing, the tool returns a structured response with `details.suggestion` rather than an error.

**Workspace discovery heuristic** — the tool walks candidate directories in this order and uses the first one that exists:
**Workspace discovery heuristic** — the tool walks candidate directories in this order and uses the first one that is a Provar workspace (i.e. contains a `.metadata` directory):

1. `<parent-of-project>/workspace-<basename>/` — sibling workspace pattern (default for Provar IDE in this workspace layout).
2. `<parent-of-project>/Provar_Workspaces/workspace-<name-dashes>/` — shared `Provar_Workspaces` directory.
3. `~/Provar/workspace-<name-dashes>/` — user-home fallback.
1. `<parent-of-project>/` — the project's parent directory (the project lives inside its workspace; default for the Provar IDE layout).
2. `<parent-of-project>/workspace-<basename>/` — sibling-workspace layout (kept for backward compatibility with earlier releases).
3. `<parent-of-project>/Provar_Workspaces/workspace-<basename>/` — shared `Provar_Workspaces` directory.
4. `~/Provar/workspace-<basename>/` — user-home fallback.

`<name-dashes>` is the project's basename with whitespace collapsed to single dashes and lowercased: `"My Project"` → `"my-project"`.
`<basename>` is the project directory's name verbatim (e.g. `"MyProject"`). A candidate is skipped unless it contains a `.metadata` directory, so candidate 1 (which almost always exists) falls through to the fallbacks when the parent is not itself a workspace. Each candidate is also checked against `--allowed-paths` before any filesystem access; candidates outside the policy are silently skipped.

**Cache layout resolution (within the discovered workspace).** The tool prefers the Provar IDE SfObject cache and falls back to the legacy/native layout:

Expand All @@ -2064,7 +2065,7 @@ Read cached Salesforce describe data for one connection from the Provar workspac
| Output field | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `requestId` | UUID for this invocation. Echoed in MCP server logs for cross-correlation. Consistent with the rest of the MCP tool surface. |
| `workspace_path` | Absolute resolved path to the discovered workspace, or `null` when none of the three candidate directories exists (or all candidates were outside `--allowed-paths`). |
| `workspace_path` | Absolute resolved path to the discovered workspace, or `null` when none of the four candidate directories is a Provar workspace (contains `.metadata`), or all candidates were outside `--allowed-paths`. |
| `cache_age_ms` | `mtime` delta in milliseconds of the connection cache directory, or `null` when the cache is missing. |
| `objects[]` | Array of `{ name, exists, required_fields, field_count, error_message? }`. `exists` is `true` (cache file present), `false` (requested but not cached), or `null` (cache miss — the whole `.metadata/<connection>` directory is absent). |
| `objects[].error_message` | Present **only** when a cache file existed but failed to parse (`exists: true, field_count: 0`). Lets the agent distinguish a corrupt / unsupported cache file from a missing one. |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@provartesting/provardx-cli",
"description": "A plugin for the Salesforce CLI to orchestrate testing activities and report quality metrics to Provar Quality Hub",
"version": "1.6.3",
"version": "1.6.4",
"mcpName": "io.github.ProvarTesting/provar",
"license": "BSD-3-Clause",
"plugins": [
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
"url": "https://github.com/ProvarTesting/provardx-cli",
"source": "github"
},
"version": "1.6.3",
"version": "1.6.4",
"packages": [
{
"registryType": "npm",
"identifier": "@provartesting/provardx-cli",
"version": "1.6.3",
"version": "1.6.4",
"transport": {
"type": "stdio"
},
Expand Down
55 changes: 29 additions & 26 deletions src/mcp/tools/orgDescribeTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,44 +78,48 @@ interface CachedObject {

// ── Workspace discovery ───────────────────────────────────────────────────────

/**
* Normalise a project basename for use in fallback workspace dir names:
* "My Project Path " → "my-project-path".
*/
export function projectNameDashes(projectPath: string): string {
return path.basename(projectPath).trim().replace(/\s+/g, '-').toLowerCase();
}

/**
* Build the ordered list of candidate workspace directories.
* First existing wins.
* 1. <parent>/workspace-<basename>/ — sibling workspace pattern.
* 2. <parent>/Provar_Workspaces/workspace-<name-dashes>/
* 3. ~/Provar/workspace-<name-dashes>/ — user-home fallback.
* The first candidate that is a real Provar workspace (contains `.metadata`) wins.
* 1. <parent>/ — the project lives INSIDE its workspace (the Provar IDE layout).
* 2. <parent>/workspace-<basename>/ — sibling-workspace layout (kept for backward compatibility).
* 3. <parent>/Provar_Workspaces/workspace-<basename>/ — shared Provar_Workspaces directory.
* 4. ~/Provar/workspace-<basename>/ — user-home fallback.
*
* The parent and sibling candidates are BOTH probed: parent is the current IDE layout,
* the sibling is the layout an earlier release assumed. Probing both keeps existing
* sibling-layout workspaces discoverable while preferring the parent layout when both exist.
*/
export function workspaceCandidates(projectPath: string): string[] {
const resolved = path.resolve(projectPath);
const parent = path.dirname(resolved);
const basename = path.basename(resolved);
const dashes = projectNameDashes(resolved);
return [
parent,
path.join(parent, `workspace-${basename}`),
path.join(parent, 'Provar_Workspaces', `workspace-${dashes}`),
path.join(os.homedir(), 'Provar', `workspace-${dashes}`),
path.join(parent, 'Provar_Workspaces', `workspace-${basename}`),
path.join(os.homedir(), 'Provar', `workspace-${basename}`),
];
}

/**
* Returns the first candidate workspace that exists AND is within allowedPaths, or null.
* Returns the first candidate that is a real Provar workspace AND is within
* allowedPaths, or null.
*
* A candidate qualifies only if it is a directory that contains a `.metadata`
* subdirectory — the marker of a Provar/Eclipse workspace. This matters because
* candidate 1 is the project's parent directory, which almost always exists: the
* `.metadata` requirement lets discovery fall through to the sibling-workspace,
* Provar_Workspaces, and ~/Provar fallbacks when the parent is not itself a workspace.
*
* Path policy is enforced PER CANDIDATE before any filesystem call: a candidate that
* sits outside `--allowed-paths` is silently skipped (it is not an error — discovery
* just moves on to the next). This means we never call fs.existsSync / fs.statSync
* against directories that the operator has explicitly placed off-limits, including
* the user-home fallback (~/Provar/...) when home sits outside the policy.
* the user-home fallback (~/Provar/workspace-<basename>/) when home sits outside the policy.
*
* When allowedPaths is empty (unrestricted mode), assertPathAllowed is a no-op and
* all candidates are probed exactly as before.
* all candidates are probed.
*/
export function discoverWorkspace(projectPath: string, allowedPaths: string[] = []): string | null {
for (const candidate of workspaceCandidates(projectPath)) {
Expand All @@ -125,12 +129,10 @@ export function discoverWorkspace(projectPath: string, allowedPaths: string[] =
// Candidate outside policy — skip without touching the filesystem.
continue;
}
try {
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
return candidate;
}
} catch {
// Permission errors etc. — skip and try next candidate
// A candidate qualifies only if it holds a `.metadata` subdirectory; this
// also implies the candidate itself is an existing directory.
if (isExistingDir(path.join(candidate, '.metadata'))) {
return candidate;
}
}
return null;
Expand Down Expand Up @@ -521,8 +523,9 @@ export function registerOrgDescribe(server: McpServer, config: ServerConfig): vo
'Read cached Salesforce describe data for one connection from the Provar workspace .metadata cache.',
'Prerequisite: the project must have been opened in Provar IDE at least once with the named connection loaded',
'(and, for the named test environment, the relevant objects expanded) — this tool is read-only and does NOT trigger a metadata download.',
'Workspace discovery tries in order: <parent>/workspace-<basename>, ',
'<parent>/Provar_Workspaces/workspace-<name-dashes>, then ~/Provar/workspace-<name-dashes>.',
'Workspace discovery uses the first of these directories that contains a .metadata dir (and is within --allowed-paths):',
'<parent> (project inside its workspace), <parent>/workspace-<basename>,',
'<parent>/Provar_Workspaces/workspace-<basename>, then ~/Provar/workspace-<basename>.',
'Within the workspace it prefers the Provar IDE SfObject cache at',
'.metadata/.plugins/com.provar.eclipse.ui/<connection>/<environment>/SfObject/<Object>.xml',
'(environment defaults to "default"; if the requested environment is absent it falls back to any cached environment),',
Expand Down
Loading