Skip to content

Commit 53f7fdc

Browse files
mrdailey99claude
andcommitted
fix(mcp): improve metadata download UX and secrets field descriptions
Three issues observed in the field: 1. provar.automation.metadata.download had a sparse description with no mention of config.load as a prerequisite or how to use the -c flag. Updated description to call out PREREQUISITE, correct flags usage (["-c", "Name1,Name2"]), and that [DOWNLOAD_ERROR] means auth failure. 2. When [DOWNLOAD_ERROR] appears in the error output (95% of the time an expired/wrong credential for the connection), the response now includes a details.suggestion field with actionable steps: check .secrets credentials, connection name spelling, scratch org expiry, and that testprojectSecrets is an encryption key not a file path. 3. testprojectSecrets in propertiesTools.ts and secrets_password in antTools.ts had descriptions easily misread as file paths. Both now explicitly say "encryption key string, not a file path". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 22f397d commit 53f7fdc

4 files changed

Lines changed: 46 additions & 6 deletions

File tree

src/mcp/tools/antTools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export function registerAntGenerate(server: McpServer, config: ServerConfig): vo
192192
.string()
193193
.default('${env.ProvarSecretsPassword}')
194194
.describe(
195-
'Password for the Provar secrets store. Defaults to reading from the ProvarSecretsPassword environment variable.'
195+
'Encryption key used to decrypt the Provar .secrets file (the password string itself, not a file path). Defaults to reading from the ProvarSecretsPassword environment variable.'
196196
),
197197
test_environment_secrets_password: z
198198
.string()

src/mcp/tools/automationTools.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,29 @@ export function registerAutomationCompile(server: McpServer): void {
233233

234234
// ── Tool: provar.automation.metadata.download ─────────────────────────────────
235235

236+
const DOWNLOAD_ERROR_SUGGESTION =
237+
'A [DOWNLOAD_ERROR] almost always means a Salesforce authentication failure for the connection being used. ' +
238+
'Check: (1) the connection credentials in the Provar project .secrets file are current and not expired; ' +
239+
'(2) the named connection exists in the project and the name is spelled correctly (case-sensitive); ' +
240+
'(3) if using a scratch org, confirm it has not expired (`sf org list`); ' +
241+
'(4) if testprojectSecrets is set in provardx-properties.json, it must be the encryption key string used to decrypt .secrets — not a file path.';
242+
236243
export function registerAutomationMetadataDownload(server: McpServer): void {
237244
server.tool(
238245
'provar.automation.metadata.download',
239-
'Download Salesforce metadata into a Provar project. Invokes `sf provar automation metadata download`.',
246+
[
247+
'Download Salesforce metadata for one or more connections into a Provar project.',
248+
'Invokes `sf provar automation metadata download`.',
249+
'PREREQUISITE: Call provar.automation.config.load first — without it the command fails with MISSING_FILE.',
250+
'Use the -c flag to specify connections: flags: ["-c", "ConnectionName1,ConnectionName2"].',
251+
'Connection names are case-sensitive and must match the names defined in the Provar project.',
252+
'If the download fails with [DOWNLOAD_ERROR], this is almost always a Salesforce authentication issue —',
253+
'check that the credentials in the project .secrets file are current and that any referenced scratch orgs have not expired.',
254+
].join(' '),
240255
{
241-
flags: z.array(z.string()).optional().default([]).describe('Raw CLI flags to forward (e.g. ["--target-org", "myorg"])'),
256+
flags: z.array(z.string()).optional().default([]).describe(
257+
'Raw CLI flags to forward. Use ["-c", "Name1,Name2"] to specify connections (required). Example: ["-c", "MyOrg,SandboxOrg"]'
258+
),
242259
sf_path: z.string().optional().describe('Path to the sf CLI executable when not in PATH (e.g. "~/.nvm/versions/node/v22.0.0/bin/sf")'),
243260
},
244261
({ flags, sf_path }) => {
@@ -247,12 +264,15 @@ export function registerAutomationMetadataDownload(server: McpServer): void {
247264

248265
try {
249266
const result = runSfCommand(['provar', 'automation', 'metadata', 'download', ...flags], sf_path);
250-
const response = { requestId, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
267+
const message = result.stderr || result.stdout;
251268

252269
if (result.exitCode !== 0) {
253-
return { isError: true as const, content: [{ type: 'text' as const, text: JSON.stringify(makeError('AUTOMATION_METADATA_FAILED', result.stderr || result.stdout, requestId)) }] };
270+
const isDownloadError = message.includes('[DOWNLOAD_ERROR]');
271+
const details: Record<string, unknown> = isDownloadError ? { suggestion: DOWNLOAD_ERROR_SUGGESTION } : {};
272+
return { isError: true as const, content: [{ type: 'text' as const, text: JSON.stringify(makeError('AUTOMATION_METADATA_FAILED', message, requestId, false, details)) }] };
254273
}
255274

275+
const response = { requestId, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
256276
return { content: [{ type: 'text' as const, text: JSON.stringify(response) }], structuredContent: response };
257277
} catch (err) {
258278
return handleSpawnError(err, requestId, 'provar.automation.metadata.download');

src/mcp/tools/propertiesTools.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ const updatesSchema = z.object({
224224
pluginOutputlevel: z.enum(['SEVERE', 'WARNING', 'INFO', 'FINE', 'FINER', 'FINEST']).optional().describe('Amount of plugin output logged'),
225225
stopOnError: z.boolean().optional().describe('Abort test run on first failure'),
226226
excludeCallable: z.boolean().optional().describe('Omit callable test cases from execution'),
227-
testprojectSecrets: z.string().optional().describe('Test project secrets encryption password'),
227+
testprojectSecrets: z.string().optional().describe(
228+
'Encryption key (password string) used to decrypt the .secrets file in the Provar project root. ' +
229+
'This is the key itself — NOT a file path. Leave empty if your project does not use secrets encryption.'
230+
),
228231
environment: z.object({
229232
testEnvironment: z.string().optional().describe('Name of the test environment to run against'),
230233
webBrowser: z.enum(['Chrome', 'Safari', 'Edge', 'Edge_Legacy', 'Firefox', 'IE', 'Chrome_Headless']).optional(),

test/unit/mcp/automationTools.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,23 @@ describe('automationTools', () => {
422422
const result = server.call('provar.automation.metadata.download', { flags: [] });
423423
assert.equal(parseBody(result).error_code, 'SF_NOT_FOUND');
424424
});
425+
426+
it('includes suggestion in details when [DOWNLOAD_ERROR] is in the message', () => {
427+
spawnStub.returns(makeSpawnResult('', 'Error (1): [DOWNLOAD_ERROR] ERROR\n', 1));
428+
const result = server.call('provar.automation.metadata.download', { flags: ['-c', 'MyOrg'] });
429+
assert.ok(isError(result));
430+
const body = parseBody(result);
431+
assert.equal(body.error_code, 'AUTOMATION_METADATA_FAILED');
432+
assert.ok(body.details && typeof (body.details as Record<string, unknown>).suggestion === 'string', 'Expected suggestion in details for DOWNLOAD_ERROR');
433+
});
434+
435+
it('does NOT include suggestion for other failure messages', () => {
436+
spawnStub.returns(makeSpawnResult('', 'Error (2): Nonexistent flag: --properties-file\n', 1));
437+
const result = server.call('provar.automation.metadata.download', { flags: [] });
438+
assert.ok(isError(result));
439+
const body = parseBody(result);
440+
assert.ok(!body.details || !(body.details as Record<string, unknown>).suggestion, 'Expected no suggestion for non-DOWNLOAD_ERROR');
441+
});
425442
});
426443

427444
// ── provar.automation.config.load ─────────────────────────────────────────

0 commit comments

Comments
 (0)