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
40 changes: 24 additions & 16 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,14 +561,17 @@ Inspects a Provar project folder and returns a structured inventory of all key p

**Input**

| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ---------------------------------------- |
| `project_path` | string | yes | Absolute path to the Provar project root |
| Parameter | Type | Required | Description |
| -------------- | --------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `project_path` | string | yes | Absolute path to the Provar project root |
| `detail` | `summary` \| `standard` \| `full` | no | Response verbosity. `"summary"` returns only `requestId`, `project_path`, `provar_home`, and `summary`. `"standard"` (default) returns full inventory. `"full"` is identical to `"standard"` for this tool. |
| `fields` | string | no | Comma-separated top-level keys to retain (e.g. `"test_case_files,summary"`). Supports dot notation for nested filtering (e.g. `"test_project.connections"`). Unknown field names are silently ignored. Applied after the `detail` filter. |

**Output** — JSON object containing:

| Field | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `requestId` | Unique identifier for this request (always present, including in `detail="summary"` responses) |
| `provar_home` | The Provar installation path, or `null` if not found |
| `provar_home_source` | Where the value came from: `"PROVAR_HOME environment variable"`, `"provardx-properties.json (<rel>)"`, or `"ANT build file (<rel>)"` |
| `provardx_properties_files` | Relative paths to any `provardx-properties.json` files found (ProvarDX CLI run configs) |
Expand Down Expand Up @@ -607,9 +610,10 @@ Lists all connections and named environments defined in the project's `.testproj

**Input**

| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ----------------------------------------------------------------- |
| `project_path` | string | yes | Absolute path to the Provar project root (within `allowed-paths`) |
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `project_path` | string | yes | Absolute path to the Provar project root (within `allowed-paths`) |
| `fields` | string | no | Comma-separated response keys to retain (e.g. `"connections,summary"`). Supports dot notation (e.g. `"connections.name,connections.type"`). Unknown fields are silently ignored. |

**Output**

Expand Down Expand Up @@ -1227,12 +1231,14 @@ Displays information about the currently connected Quality Hub org. Invokes `sf

**Input**

| Parameter | Type | Required | Description |
| ------------ | -------- | -------- | ------------------------------------------ |
| `target_org` | string | no | SF CLI org alias (uses default if omitted) |
| `flags` | string[] | no | Additional raw CLI flags |
| Parameter | Type | Required | Description |
| ------------ | --------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target_org` | string | no | SF CLI org alias (uses default if omitted) |
| `flags` | string[] | no | Additional raw CLI flags |
| `detail` | `summary` \| `standard` \| `full` | no | Response verbosity. `"summary"` returns only `requestId` and `exitCode`. `"standard"` (default) returns `requestId`, `exitCode`, `stdout`, and `stderr`. |
| `fields` | string | no | Comma-separated response keys to retain (e.g. `"exitCode,stdout"`). Unknown fields are silently ignored. Applied after the `detail` filter. |

**Output** — `{ requestId, exitCode, stdout, stderr }`
**Output** — `{ requestId, exitCode, stdout, stderr }`. Use `detail="summary"` to reduce to `{ requestId, exitCode }` only, or pass `fields` to select specific keys.

---

Expand Down Expand Up @@ -1297,12 +1303,14 @@ Retrieves test cases from Quality Hub by user story or metadata component. Invok

**Input**

| Parameter | Type | Required | Description |
| ------------ | -------- | -------- | ------------------------------------------------------------------------------------ |
| `target_org` | string | yes | SF CLI org alias or username |
| `flags` | string[] | no | Additional raw CLI flags (e.g. `["--issues", "US-123", "--test-project", "MyProj"]`) |
| Parameter | Type | Required | Description |
| ------------ | --------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target_org` | string | yes | SF CLI org alias or username |
| `flags` | string[] | no | Additional raw CLI flags (e.g. `["--issues", "US-123", "--test-project", "MyProj"]`) |
| `detail` | `summary` \| `standard` \| `full` | no | Response verbosity. `"summary"` returns only `requestId` and `exitCode`. `"standard"` (default) returns `requestId`, `exitCode`, `stdout`, and `stderr`. |
| `fields` | string | no | Comma-separated response keys to retain (e.g. `"exitCode,stdout"`). Unknown fields are silently ignored. Applied after the `detail` filter. |

**Output** — `{ requestId, exitCode, stdout, stderr }`
**Output** — `{ requestId, exitCode, stdout, stderr }`. Use `detail="summary"` to reduce to `{ requestId, exitCode }` only, or pass `fields` to select specific keys.

**Error codes:** `QH_RETRIEVE_FAILED`, `SF_NOT_FOUND`

Expand Down
22 changes: 20 additions & 2 deletions src/mcp/tools/connectionTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { ServerConfig } from '../server.js';
import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js';
import { makeError, makeRequestId } from '../schemas/common.js';
import { log } from '../logging/logger.js';
import { maskFields, parseFieldsParam } from '../utils/fieldMask.js';
import { desc } from './descHelper.js';

// ── Types ─────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -155,9 +156,20 @@ export function registerConnectionList(server: McpServer, config: ServerConfig):
'string, absolute path to project root'
)
),
fields: z
.string()
.optional()
.describe(
desc(
'Comma-separated list of top-level response keys to retain (e.g. "connections,summary"). ' +
'Supports dot notation for nested filtering (e.g. "connections.name,connections.type"). ' +
'Unknown field names are silently ignored. Omit for full response.',
'string, optional; comma-separated keys to keep (supports dot notation)'
)
),
},
},
({ project_path }) => {
({ project_path, fields }) => {
const requestId = makeRequestId();
log('info', 'provar_connection_list', { requestId, project_path });

Expand Down Expand Up @@ -195,7 +207,7 @@ export function registerConnectionList(server: McpServer, config: ServerConfig):
const connections = parseConnectionList(content);
const environments = parseEnvironmentList(content);

const result = {
let result: Record<string, unknown> = {
requestId,
project_path: resolvedPath,
connections,
Expand All @@ -205,6 +217,12 @@ export function registerConnectionList(server: McpServer, config: ServerConfig):
environment_count: environments.length,
},
};

const fieldList = parseFieldsParam(fields);
if (fieldList) {
result = maskFields(result, fieldList) as Record<string, unknown>;
}

return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
structuredContent: result,
Expand Down
41 changes: 39 additions & 2 deletions src/mcp/tools/projectInspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import type { ServerConfig } from '../server.js';
import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js';
import { makeError, makeRequestId } from '../schemas/common.js';
import { log } from '../logging/logger.js';
import { applyDetailLevel, type DetailLevel } from '../utils/detailLevel.js';
import { maskFields, parseFieldsParam } from '../utils/fieldMask.js';
import { desc } from './descHelper.js';

const INSPECT_SUMMARY_FIELDS = ['requestId', 'project_path', 'provar_home', 'summary'];

export function registerProjectInspect(server: McpServer, config: ServerConfig): void {
server.registerTool(
'provar_project_inspect',
Expand Down Expand Up @@ -45,9 +49,31 @@ export function registerProjectInspect(server: McpServer, config: ServerConfig):
'string, absolute path to project root'
)
),
detail: z
.enum(['summary', 'standard', 'full'])
.optional()
.default('standard')
.describe(
desc(
'Response verbosity: "summary" returns only requestId, project_path, provar_home, and the summary object; ' +
'"standard" (default) returns the full inventory; "full" is identical to standard for this tool.',
'enum summary|standard|full, optional; default standard'
)
),
fields: z
.string()
.optional()
.describe(
desc(
'Comma-separated list of top-level keys to retain (e.g. "test_case_files,summary"). ' +
'Supports dot notation for nested filtering (e.g. "test_project.connections"). ' +
'Unknown field names are silently ignored. Applied after the detail filter.',
'string, optional; comma-separated keys to keep (supports dot notation)'
)
),
},
},
({ project_path }) => {
({ project_path, detail, fields }) => {
const requestId = makeRequestId();
log('info', 'provar_project_inspect', { requestId, project_path });

Expand All @@ -60,7 +86,18 @@ export function registerProjectInspect(server: McpServer, config: ServerConfig):
return { isError: true, content: [{ type: 'text' as const, text: JSON.stringify(err) }] };
}

const result = buildProjectInventory(resolved, requestId);
let result = buildProjectInventory(resolved, requestId);

const detailLevel = (detail ?? 'standard') as DetailLevel;
if (detailLevel !== 'standard') {
result = applyDetailLevel(result, detailLevel, INSPECT_SUMMARY_FIELDS);
}

const fieldList = parseFieldsParam(fields);
if (fieldList) {
result = maskFields(result, fieldList) as typeof result;
}

return {
content: [{ type: 'text' as const, text: JSON.stringify(result) }],
structuredContent: result,
Expand Down
70 changes: 66 additions & 4 deletions src/mcp/tools/qualityHubTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { makeError, makeRequestId } from '../schemas/common.js';
import { log } from '../logging/logger.js';
import { applyDetailLevel, type DetailLevel } from '../utils/detailLevel.js';
import { maskFields, parseFieldsParam } from '../utils/fieldMask.js';
import { runSfCommand } from './sfSpawn.js';
import { desc } from './descHelper.js';

Expand All @@ -31,6 +33,8 @@ function handleSpawnError(
};
}

const QH_SUMMARY_FIELDS = ['requestId', 'exitCode'];

// ── Tool: provar_qualityhub_connect ───────────────────────────────────────────

export function registerQualityHubConnect(server: McpServer): void {
Expand Down Expand Up @@ -131,9 +135,24 @@ export function registerQualityHubDisplay(server: McpServer): void {
'string, optional; path to sf CLI executable'
)
),
detail: z
.enum(['summary', 'standard', 'full'])
.optional()
.default('standard')
.describe(
'Response verbosity: "summary" returns only requestId and exitCode; ' +
'"standard" (default) returns requestId, exitCode, stdout, and stderr.'
),
fields: z
.string()
.optional()
.describe(
'Comma-separated list of response keys to retain (e.g. "exitCode,stdout"). ' +
'Unknown field names are silently ignored. Applied after the detail filter.'
),
},
},
({ target_org, flags, sf_path }) => {
({ target_org, flags, sf_path, detail, fields }) => {
const requestId = makeRequestId();
log('info', 'provar_qualityhub_display', { requestId, target_org });

Expand All @@ -142,7 +161,12 @@ export function registerQualityHubDisplay(server: McpServer): void {
if (target_org) args.splice(3, 0, '--target-org', target_org);

const result = runSfCommand(args, sf_path);
const response = { requestId, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
let response: Record<string, unknown> = {
requestId,
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
};

if (result.exitCode !== 0) {
return {
Expand All @@ -156,6 +180,15 @@ export function registerQualityHubDisplay(server: McpServer): void {
};
}

const detailLevel = (detail ?? 'standard') as DetailLevel;
if (detailLevel !== 'standard') {
response = applyDetailLevel(response, detailLevel, QH_SUMMARY_FIELDS);
}
const fieldList = parseFieldsParam(fields);
if (fieldList) {
response = maskFields(response, fieldList) as Record<string, unknown>;
}

return { content: [{ type: 'text' as const, text: JSON.stringify(response) }], structuredContent: response };
} catch (err) {
return handleSpawnError(err, requestId, 'provar_qualityhub_display');
Expand Down Expand Up @@ -441,9 +474,24 @@ export function registerQualityHubTestcaseRetrieve(server: McpServer): void {
'string, optional; path to sf CLI executable'
)
),
detail: z
.enum(['summary', 'standard', 'full'])
.optional()
.default('standard')
.describe(
'Response verbosity: "summary" returns only requestId and exitCode; ' +
'"standard" (default) returns requestId, exitCode, stdout, and stderr.'
),
fields: z
.string()
.optional()
.describe(
'Comma-separated list of response keys to retain (e.g. "exitCode,stdout"). ' +
'Unknown field names are silently ignored. Applied after the detail filter.'
),
},
},
({ target_org, flags, sf_path }) => {
({ target_org, flags, sf_path, detail, fields }) => {
const requestId = makeRequestId();
log('info', 'provar_qualityhub_testcase_retrieve', { requestId, target_org });

Expand All @@ -452,7 +500,12 @@ export function registerQualityHubTestcaseRetrieve(server: McpServer): void {
['provar', 'quality-hub', 'testcase', 'retrieve', '--target-org', target_org, ...flags],
sf_path
);
const response = { requestId, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
let response: Record<string, unknown> = {
requestId,
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
};

if (result.exitCode !== 0) {
return {
Expand All @@ -466,6 +519,15 @@ export function registerQualityHubTestcaseRetrieve(server: McpServer): void {
};
}

const detailLevel = (detail ?? 'standard') as DetailLevel;
if (detailLevel !== 'standard') {
response = applyDetailLevel(response, detailLevel, QH_SUMMARY_FIELDS);
}
const fieldList = parseFieldsParam(fields);
if (fieldList) {
response = maskFields(response, fieldList) as Record<string, unknown>;
}

return { content: [{ type: 'text' as const, text: JSON.stringify(response) }], structuredContent: response };
} catch (err) {
return handleSpawnError(err, requestId, 'provar_qualityhub_testcase_retrieve');
Expand Down
Loading
Loading