feat(mcp): wave 2 — non-SF generation fixes + provar.connection.list tool#134
Merged
mrdailey99 merged 2 commits intodevelopfrom Apr 24, 2026
Merged
feat(mcp): wave 2 — non-SF generation fixes + provar.connection.list tool#134mrdailey99 merged 2 commits intodevelopfrom
mrdailey99 merged 2 commits intodevelopfrom
Conversation
…n.list Req: Wave 2 engineering review — fix non-SF page object generation bugs and add connection discovery tool for AI agents Fix: correct URI format, substeps XML structure, uiWithScreenTarget validator, SSO guard, wildcard scope, connection.list tool - testCaseGenerate: query-param URI (ui:pageobject:target?pageId=pageobjects.X) and substeps clause - bestPracticesEngine: implement uiWithScreenTarget validator, fix arguments path - pageObjectGenerate: FILE_EXISTS guard on SSO stub write - qualityHubTools: narrow wildcard detection to --plan-name values only - connectionTools: new provar.connection.list reads .testproject connections and environments Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f990b20 to
2d4240c
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR expands and hardens the MCP toolset for Provar DX (“wave 2”), addressing generation/validation issues for non-Salesforce targets, tightening Quality Hub CLI flag handling, and adding a new connection discovery tool.
Changes:
- Update
provar.testcase.generateto support URI-aware nesting (non-SF page objects viaUiWithScreen) and add optional post-generation validation (validate_after_edit). - Fix
bestPracticesEngineby registering the missinguiWithScreenTargetvalidator and correcting UiWithScreen target parsing. - Add
provar.connection.listto parse.testprojectconnections/environments, plus tests/docs/smoke coverage; also add SSO stub overwrite protection inprovar.pageobject.generateand narrow Quality Hub wildcard warnings.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/mcp/tools/testCaseGenerate.ts |
Adds target_uri nesting + validate_after_edit behavior and refactors XML building. |
test/unit/mcp/testCaseGenerate.test.ts |
Adds coverage for non-SF ui: nesting and validation field behavior. |
src/mcp/tools/bestPracticesEngine.ts |
Implements and registers uiWithScreenTarget validator and target extraction fix. |
test/unit/mcp/bestPracticesEngine.test.ts |
Adds uiWithScreenTarget validator tests. |
src/mcp/tools/pageObjectGenerate.ts |
Adds sso_class stub generation and guards against overwriting the stub. |
test/unit/mcp/pageObjectGenerate.test.ts |
Adds tests for SSO stub generation, path policy, and FILE_EXISTS behavior. |
src/mcp/tools/qualityHubTools.ts |
Narrows wildcard warning detection scope and returns warnings via details. |
test/unit/mcp/qualityHubTools.test.ts |
Adds tests for wildcard warning behavior and reformats existing cases. |
src/mcp/tools/connectionTools.ts |
New tool to list connections/environments from .testproject. |
test/unit/mcp/connectionTools.test.ts |
New tests for connection listing, path policy, missing file handling. |
src/mcp/server.ts |
Registers the new connection tools. |
scripts/mcp-smoke.cjs |
Adds smoke coverage for provar.connection.list and updates expected response count. |
docs/mcp.md |
Documents provar.connection.list and updates existing tool docs for new options/outputs. |
TODOS.md |
Tracks known follow-up work around partial-write error handling in page object generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+148
to
+166
| try { | ||
| const resolvedPath = path.resolve(project_path); | ||
| assertPathAllowed(resolvedPath, config.allowedPaths); | ||
|
|
||
| const testProjectPath = path.join(resolvedPath, '.testproject'); | ||
| if (!fs.existsSync(testProjectPath)) { | ||
| const err = makeError( | ||
| 'CONNECTION_FILE_NOT_FOUND', | ||
| `No .testproject file found at: ${testProjectPath}. Run provar.project.validate first to confirm the project structure.`, | ||
| requestId, | ||
| false, | ||
| { suggestion: 'Run provar.project.validate with the project_path to confirm the project root, then retry.' } | ||
| ); | ||
| return { isError: true, content: [{ type: 'text' as const, text: JSON.stringify(err) }] }; | ||
| } | ||
|
|
||
| let content: string; | ||
| try { | ||
| content = fs.readFileSync(testProjectPath, 'utf-8'); |
Comment on lines
+63
to
+72
| function parseTestProjectXml(content: string): Record<string, unknown> { | ||
| let parsed: Record<string, unknown>; | ||
| try { | ||
| parsed = TP_PARSER.parse(content) as Record<string, unknown>; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| const raw = parsed['testProject']; | ||
| return raw !== null && typeof raw === 'object' ? (raw as Record<string, unknown>) : {}; | ||
| } |
Comment on lines
+127
to
+140
| @@ -110,24 +128,42 @@ export function registerPageObjectGenerate(server: McpServer, config: ServerConf | |||
| fs.writeFileSync(filePath, javaSource, 'utf-8'); | |||
| written = true; | |||
| log('info', 'provar.pageobject.generate: wrote file', { requestId, filePath }); | |||
|
|
|||
| if (ssoSource && ssoFilePath) { | |||
| if (fs.existsSync(ssoFilePath) && !input.overwrite) { | |||
| const err = makeError( | |||
| 'FILE_EXISTS', | |||
| `SSO stub file already exists: ${ssoFilePath}. Set overwrite=true to replace.`, | |||
| requestId | |||
| ); | |||
| return { isError: true, content: [{ type: 'text' as const, text: JSON.stringify(err) }] }; | |||
| } | |||
Comment on lines
136
to
+147
| 'provar.qualityhub.testrun', | ||
| 'Trigger a Quality Hub test run. Invokes `sf provar quality-hub test run`.', | ||
| 'Trigger a Quality Hub test run. Invokes `sf provar quality-hub test run`. ' + | ||
| 'Warning: wildcard characters (* or ?) in flag values will cause QH plan-level reporting to be skipped — use exact plan names.', | ||
| { | ||
| target_org: z.string().describe('SF org alias or username'), | ||
| flags: z.array(z.string()).optional().default([]).describe('Additional raw CLI flags (e.g. ["--plan-name", "SmokeTests"])'), | ||
| flags: z | ||
| .array(z.string()) | ||
| .optional() | ||
| .default([]) | ||
| .describe( | ||
| 'Additional raw CLI flags (e.g. ["--plan-name", "SmokeTests"]). Avoid wildcards in plan names — they skip QH plan-level reporting.' | ||
| ), |
Comment on lines
+580
to
+593
| | Parameter | Type | Required | Description | | ||
| | --------------------------- | ------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------- | | ||
| | `class_name` | string | yes | PascalCase Java class name (e.g. `AccountDetailPage`) | | ||
| | `package_name` | string | yes | Java package (e.g. `pageobjects.accounts`) | | ||
| | `page_type` | `standard` \| `salesforce` | yes | Generates `@Page` or `@SalesforcePage` annotation | | ||
| | `title` | string | no | Page title for the annotation | | ||
| | `connection_name` | string | no | Salesforce connection name (for `@SalesforcePage`) | | ||
| | `salesforce_page_attribute` | string | no | Additional Salesforce page attribute | | ||
| | `fields` | array of `{ name, type, locator_type, locator_value }` | no | WebElement field definitions | | ||
| | `sso_class` | string | no | PascalCase class name for an `ILoginPage` stub (non-SF SSO). Written alongside the page object on disk. | | ||
| | `output_path` | string | no | Full file path to write (must be within `allowed-paths`) | | ||
| | `overwrite` | boolean | no | Overwrite existing file (default: `false`) | | ||
| | `dry_run` | boolean | no | Return content without writing to disk | | ||
| | `idempotency_key` | string | no | Prevents duplicate generation for the same key | |
Comment on lines
+641
to
+651
| | Parameter | Type | Required | Description | | ||
| | --------------------- | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | ||
| | `test_case_name` | string | yes | Human-readable test case name | | ||
| | `test_case_id` | string | no | Custom test case ID (auto-generated if omitted) | | ||
| | `steps` | array of `{ api_id, name, arguments? }` | no | Step definitions | | ||
| | `target_uri` | string | no | Page object URI. `ui:pageobject:target?pageId=pageobjects.X` triggers `UiWithScreen` nesting; `sf:` or absent → flat. | | ||
| | `output_path` | string | no | File path to write (must be within `allowed-paths`) | | ||
| | `overwrite` | boolean | no | Overwrite existing file (default: `false`) | | ||
| | `dry_run` | boolean | no | Return XML without writing to disk | | ||
| | `validate_after_edit` | boolean | no | Run structural validation after generation (default: `true`). Returns `TESTCASE_INVALID` if invalid. Set `false` to skip. | | ||
| | `idempotency_key` | string | no | Prevents duplicate generation for the same key | |
Comment on lines
+204
to
+212
| const errResult = makeError( | ||
| 'TESTCASE_INVALID', | ||
| `Generated test case failed structural validation (${validationFull.error_count} error(s)). See validation field.`, | ||
| requestId, | ||
| false, | ||
| { validation: validationSlim } | ||
| ); | ||
| log('warn', 'provar.testcase.generate: TESTCASE_INVALID', { requestId }); | ||
| return { isError: true, content: [{ type: 'text' as const, text: JSON.stringify(errResult) }] }; |
| for (const call of getAllApiCalls(tc)) { | ||
| const apiId = call['@_apiId'] as string | undefined; | ||
| if (!apiId) continue; | ||
| if (targetApiId && !apiId.includes('UiWithScreen')) continue; |
Comment on lines
1096
to
1106
| Triggers a Quality Hub test run. Invokes `sf provar quality-hub test run`. Returns the test run ID which can be passed to `provar.qualityhub.testrun.report` to poll for results. | ||
|
|
||
| > **Wildcard warning:** if any value in `flags` contains `*` or `?`, the tool adds `details.warning` explaining that QH plan-level reporting will be skipped. Execution still proceeds — the warning is non-blocking. | ||
|
|
||
| **Input** | ||
|
|
||
| | Parameter | Type | Required | Description | | ||
| | ------------ | -------- | -------- | ----------------------------------------------------------------------------- | | ||
| | `target_org` | string | yes | SF CLI org alias or username | | ||
| | `flags` | string[] | no | Additional raw CLI flags (e.g. `["--configuration-file", "config/run.json"]`) | | ||
| | Parameter | Type | Required | Description | | ||
| | ------------ | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `target_org` | string | yes | SF CLI org alias or username | | ||
| | `flags` | string[] | no | Additional raw CLI flags (e.g. `["--plan-name", "SmokeTests"]`). Avoid wildcards in plan names — they skip QH plan-level reporting. | | ||
|
|
Req: Address all 9 PR review comments and 2 of 3 Codex findings on feature/wave2-nonsf-generation Fix: atomic write ordering, XML parse error surfacing, path validation, validator logic, error message clarity, and doc field name corrections - pageObjectGenerate: preflight both filePath and ssoFilePath before any write (atomic) - connectionTools: throw CONNECTION_XML_PARSE_ERROR on malformed .testproject XML - connectionTools: assertPathAllowed on testProjectPath (defence in depth) - bestPracticesEngine: use rule.check.apiId value for filter instead of hardcoded substring - testCaseGenerate: clarify TESTCASE_INVALID message to reference details.validation - qualityHubTools: narrow wildcard flag description to --plan-name values specifically - docs/mcp.md: fix fields schema (locator_strategy/element_type), steps schema (attributes), QH wildcard note Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
<clauses><clause name="substeps">, and non-SF target URIs now use query-param format (ui:pageobject:target?pageId=pageobjects.X) instead of colon formatuiWithScreenTargetvalidator (was silently passing all test cases due to missing VALIDATOR_REGISTRY entry); fixedgetUiWithScreenTargetto read the correct XML path (arguments/argumentnotargument)FILE_EXISTSguard on SSO stub write, matching the existing guard on the main.javafile — prevents silent overwrite and gives agents a clear retry path--plan-namevalues; previously flagged unrelated flag valuesprovar.connection.list— reads connections and named environments from the.testprojectXML usingfast-xml-parser; returns type, URL, SSO status; never exposes credential values from.secretsTests
connectionTools.test.ts(happy path, path policy, missing file, XML parsing edge cases),bestPracticesEngine.test.ts(4 uiWithScreenTarget tests),pageObjectGenerate.test.ts(FILE_EXISTS on SSO stub),qualityHubTools.test.ts(wildcard scope fix)Test plan
provar.testcase.generatewithpage_object_uriproduces<clauses><clause name="substeps">wrapping inner stepsui:pageobject:target?pageId=pageobjects.<Class>formatprovar.testcase.validatefires theuiWithScreenTargetrule on colon-format URIsprovar.pageobject.generatewithsso_classreturnsFILE_EXISTSwhen SSO stub already existsprovar.connection.listreturns correct connections and environments from a real.testproject🤖 Generated with Claude Code