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
31 changes: 31 additions & 0 deletions docs/mcp-pilot-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,37 @@ If any FAIL indicator appears, report it to the Provar team with the prompt and

---

### Scenario 13: Org-Aware Test Case Generation

The `provar_org_describe` tool surfaces cached Salesforce describe data from the Provar IDE workspace so the agent knows which fields on each object are required and what their types are — without making a live Salesforce API call. Use it as a hint source before generating data-heavy steps.

**Prerequisite:** the project must have been opened in Provar IDE at least once with the named connection loaded, so the `.metadata/<connection_name>/` directory is populated.

**Try this prompt:**

> "Before generating a test case that creates an Account, call `provar_org_describe` against my project at `/path/to/MyProject` for connection `MyOrg` and the `Account` object only. Use the required-field list to populate the create form."

The tool returns the discovered workspace path, a cache age, and per-object required-field metadata. Example call:

```jsonc
{
"project_path": "/Users/you/git/MyProject",
"connection_name": "MyOrg",
"objects": ["Account"],
"field_filter": "required"
}
```

**What to look for (PASS):**

- Response includes `workspace_path` resolved to a real `workspace-*` directory.
- `objects[0].required_fields` contains at least one field with `nillable: false`.
- The follow-up `provar_testcase_generate` call uses field names from the response.

**Cache-miss behaviour (also PASS):** if the cache directory does not exist the tool returns `details.suggestion` telling the agent how to recover — either open the project in Provar IDE to populate the cache, or pass field-type hints inline.

---

## Security Model

### What the server does
Expand Down
150 changes: 141 additions & 9 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,17 @@ The Provar DX CLI ships with a built-in **Model Context Protocol (MCP) server**
- [provar_testplan_add-instance](#provar_testplan_add-instance)
- [provar_testplan_create-suite](#provar_testplan_create-suite)
- [provar_testplan_remove-instance](#provar_testplan_remove-instance)
- [Data-driven execution](#data-driven-execution)
- [NitroX — Hybrid Model page objects](#nitrox--hybrid-model-page-objects)
- [provar_nitrox_discover](#provar_nitrox_discover)
- [provar_nitrox_read](#provar_nitrox_read)
- [provar_nitrox_validate](#provar_nitrox_validate)
- [provar_nitrox_generate](#provar_nitrox_generate)
- [provar_nitrox_patch](#provar_nitrox_patch)
- [Quality Hub API tools](#quality-hub-api-tools)
- [provar_qualityhub_examples_retrieve](#provar_qualityhub_examples_retrieve)
- [Org metadata access](#org-metadata-access)
- [provar_org_describe](#provar_org_describe)
- [Data-driven execution](#data-driven-execution)
- [NitroX — Hybrid Model page objects](#nitrox--hybrid-model-page-objects)
- [provar_nitrox_discover](#provar_nitrox_discover)
- [provar_nitrox_read](#provar_nitrox_read)
- [provar_nitrox_validate](#provar_nitrox_validate)
- [provar_nitrox_generate](#provar_nitrox_generate)
- [provar_nitrox_patch](#provar_nitrox_patch)
- [Quality Hub API tools](#quality-hub-api-tools)
- [provar_qualityhub_examples_retrieve](#provar_qualityhub_examples_retrieve)
- [Org metadata via Salesforce Hosted MCP](#org-metadata-via-salesforce-hosted-mcp)
- [MCP Prompts](#mcp-prompts)
- [Migration prompts](#migration-prompts)
Expand Down Expand Up @@ -1769,6 +1771,136 @@ The constraint is also referenced in the [`provar_testcase_generate`](#provar_te

---

## Org metadata access

Tools that surface Salesforce org metadata to authoring tools without making a live API call. These read from data that has already been written to disk by the Provar IDE — they do **not** trigger metadata downloads themselves and they do **not** require an authenticated session.

> **Distinct from `.provarCaches`:** the runtime cache used by `provar_automation_testrun` lives at `<resultsPath>/.provarCaches/` and is regenerated per run. The cache read by `provar_org_describe` lives in the Provar IDE **workspace** (`<workspace>/.metadata/<connection_name>/`) and is updated when a user opens the project and loads a connection in the IDE.

### `provar_org_describe`

Read cached Salesforce describe data for one connection from the Provar workspace `.metadata` cache. Returns the object list, required-field schema, and a cache age. Use this before calling `provar_testcase_generate` so the generator can produce steps with correctly-typed field values.

**Prerequisite:** the project must have been opened in Provar IDE at least once with the named connection loaded. 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:

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.

`<name-dashes>` is the project's basename with whitespace collapsed to single dashes and lowercased: `"My Project"` → `"my-project"`.

| Input | Type | Required | Default | Description |
| ----------------- | ----------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `project_path` | string | yes | — | Absolute path to the Provar test project root (the directory containing `.testproject`). Must be within `--allowed-paths`. |
| `connection_name` | string | yes | — | Connection name as defined in `.testproject` (e.g. `"MyOrg"`). Must match the `.metadata` subdirectory exactly. Path separators in this value are rejected (`PATH_TRAVERSAL`). |
| `objects` | string[] | no | all | Filter — only return data for these object API names. When omitted, lists every object cached under the connection directory. |
| `field_filter` | `'required'` \| `'all'` | no | `'required'` | Which fields to return. `'required'` includes only fields with `nillable=false`; `'all'` returns every cached field. |

| 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`). |
| `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. |
| `details.suggestion` | Present **only** on cache miss. Tells the agent how to populate the cache (open Provar IDE) or how to proceed without it (inline hints). |

**Example — happy path:**

```jsonc
// Request
{
"project_path": "/Users/me/git/MyProject",
"connection_name": "MyOrg",
"objects": ["Account", "Contact"],
"field_filter": "required"
}

// Response
{
"requestId": "01HEXX...K7P",
"workspace_path": "/Users/me/git/workspace-MyProject",
"cache_age_ms": 1839200,
"objects": [
{
"name": "Account",
"exists": true,
"required_fields": [
{ "name": "Name", "type": "string", "default_value": null, "nillable": false }
],
"field_count": 24
},
{
"name": "Contact",
"exists": true,
"required_fields": [
{ "name": "LastName", "type": "string", "default_value": null, "nillable": false }
],
"field_count": 31
}
]
}
```

**Example — cache miss:**

```jsonc
// Response when the .metadata/<connection_name> directory does not exist
{
"requestId": "01HEXX...K7P",
"workspace_path": "/Users/me/git/workspace-MyProject",
"cache_age_ms": null,
"objects": [{ "name": "Account", "exists": null, "required_fields": [], "field_count": 0 }],
"details": {
"suggestion": "Open this project in Provar IDE and load the 'MyOrg' connection, or pass field-type hints inline to provar_testcase_generate."
}
}
```

**Example — parse error on a cached file:**

```jsonc
// Response when Account.json exists but is corrupt / unparseable
{
"requestId": "01HEXX...K7P",
"workspace_path": "/Users/me/git/workspace-MyProject",
"cache_age_ms": 1839200,
"objects": [
{
"name": "Account",
"exists": true,
"required_fields": [],
"field_count": 0,
"error_message": "Failed to parse cache file (Account.json): Unexpected token } in JSON at position 42"
}
]
}
```

**On-disk cache schema (one file per object).** The tool reads `.json` first, then `.xml`, then `.object` as a fallback:

```jsonc
// <workspace>/.metadata/<connection_name>/Account.json
{
"name": "Account",
"fields": [
{ "name": "Name", "type": "string", "defaultValue": null, "nillable": false },
{ "name": "Phone", "type": "phone", "defaultValue": null, "nillable": true }
]
}
```

**Error codes:**

| Code | Meaning |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| `PATH_NOT_ALLOWED` | `project_path` or the resolved workspace path is outside `--allowed-paths`. |
| `PATH_TRAVERSAL` | `project_path` contains `..` segments, or `connection_name` contains a path separator or `..`. |

---

## NitroX — Hybrid Model page objects

NitroX is Provar's **Hybrid Model** for locators. Instead of hand-written Java Page Objects it uses component-based `.po.json` files that map UI elements for any Salesforce component type: LWC, Screen Flow, Industry / OmniStudio, Experience Cloud, and standard HTML5. These files live in `nitroX/` directories inside your Provar project.
Expand Down
30 changes: 30 additions & 0 deletions scripts/mcp-smoke.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,36 @@ async function runTests() {
test_item_id: '1',
});

// ── 54. provar_org_describe — cache miss ─────────────────────────────────
// TMP has no workspace at all → cache-miss response with details.suggestion
if (inGroup('inspect'))
await callTool('provar_org_describe', {
project_path: TMP,
connection_name: 'SmokeOrg',
objects: ['Account'],
});

// ── 55. provar_org_describe — happy path ─────────────────────────────────
// Set up a sibling workspace + .metadata/<connection> with one fake object.
if (inGroup('inspect')) {
const fs = require('fs');
const orgProject = path.join(TMP, 'org-describe-smoke-project');
fs.mkdirSync(orgProject, { recursive: true });
const cxnDir = path.join(TMP, 'workspace-org-describe-smoke-project', '.metadata', 'SmokeOrg');
fs.mkdirSync(cxnDir, { recursive: true });
fs.writeFileSync(
path.join(cxnDir, 'Account.json'),
JSON.stringify({
name: 'Account',
fields: [{ name: 'Name', type: 'string', defaultValue: null, nillable: false }],
})
);
await callTool('provar_org_describe', {
project_path: orgProject,
connection_name: 'SmokeOrg',
});
}

server.stdin.end();
}

Expand Down
3 changes: 2 additions & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { registerAllTestPlanTools } from './tools/testPlanTools.js';
import { registerAllNitroXTools } from './tools/nitroXTools.js';
import { registerAllTestCaseStepTools } from './tools/testCaseStepTools.js';
import { registerAllConnectionTools } from './tools/connectionTools.js';
import { registerAllOrgDescribeTools } from './tools/orgDescribeTools.js';
import { registerAllPrompts } from './prompts/index.js';
import {
createDepthGuardState,
Expand Down Expand Up @@ -64,7 +65,7 @@ const TOOL_GROUPS: Record<string, Array<(server: McpServer, config: ServerConfig
registerAllTestCaseStepTools,
registerAllTestPlanTools,
],
inspect: [registerProjectInspect],
inspect: [registerProjectInspect, registerAllOrgDescribeTools],
connection: [registerAllConnectionTools],
rca: [registerAllRcaTools],
};
Expand Down
Loading
Loading