Skip to content
Open
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
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
## Harness MCP Server 2.0

An MCP (Model Context Protocol) server that gives AI agents full access to the Harness.io platform through 11 consolidated tools and 215 resource types.
An MCP (Model Context Protocol) server that gives AI agents full access to the Harness.io platform through 11 consolidated tools and 216 resource types.

## Why Use This MCP Server

Most MCP servers map one tool per API endpoint. For a platform as broad as Harness, that means 240+ tools — and LLMs get worse at tool selection as the count grows. Context windows fill up with schemas, and every new endpoint means new code.

This server is built differently:

- **11 tools, 215 resource types.** A registry-based dispatch system routes `harness_list`, `harness_get`, `harness_create`, etc. to any Harness resource — pipelines, services, environments, orgs, projects, feature flags, cost data, and more. The LLM picks from 11 tools instead of hundreds.
- **11 tools, 216 resource types.** A registry-based dispatch system routes `harness_list`, `harness_get`, `harness_create`, etc. to any Harness resource — pipelines, services, environments, orgs, projects, feature flags, cost data, and more. The LLM picks from 11 tools instead of hundreds.
- **Full platform coverage.** 37 default toolsets spanning CI/CD, GitOps, Feature Flags, Cloud Cost Management, Security Testing, Chaos Engineering, Database DevOps, Internal Developer Portal, Software Supply Chain, Infrastructure as Code Management, Governance, Service Overrides, Knowledge Graph, and more. Opt-in Ansible coverage is available when you need inventory and playbook data.
- **Multi-project workflows out of the box.** Agents discover organizations and projects dynamically — no hardcoded env vars needed. Ask "show failed executions across all projects" and the agent can navigate the full account hierarchy.
- **34 prompt templates.** Pre-built prompts for common workflows: build & deploy apps end-to-end, debug failed pipelines, review DORA metrics, triage vulnerabilities, optimize cloud costs, audit access control, plan feature flag rollouts, review pull requests, approve pending pipelines, and more.
Expand Down Expand Up @@ -1197,7 +1197,7 @@ Harness pipelines can be stored in three ways:

## Resource Types

215 resource types organized across 37 toolsets. Each resource type supports a subset of CRUD operations and optional execute actions.
216 resource types organized across 37 toolsets. Each resource type supports a subset of CRUD operations and optional execute actions.

### Platform

Expand Down Expand Up @@ -1599,11 +1599,12 @@ SEI resources are consolidated for token efficiency. Use `metric` or `aspect` pa
### Security Testing Orchestration (STO)


| Resource Type | List | Get | Create | Update | Delete | Execute Actions |
| ----------------------- | ---- | --- | ------ | ------ | ------ | ------------------------------ |
| `security_issue` | x | | | | | |
| `security_issue_filter` | x | | | | | |
| `security_exemption` | x | | x | | | `approve`, `reject` |
| Resource Type | List | Get | Create | Update | Delete | Execute Actions |
| ------------------------- | ---- | --- | ------ | ------ | ------ | ------------------------------ |
| `security_issue` | x | | | | | |
| `security_issue_filter` | x | | | | | |
| `security_exemption` | x | | x | | | `approve`, `reject` |
| `sast_remediation_diff` | x | | | | | |

`security_exemption` create is a `high_write` operation. The server derives `requester_id` from the authenticated PAT, sets `exemptFutureOccurrences=true`, and defaults `duration_days` to 30 when not provided. For listing exemptions, pass a small explicit page size (for example `filters: { "status": "Pending", "size": 5 }`) and follow the `_nextPageHint` returned in each response.

Expand Down Expand Up @@ -1807,7 +1808,7 @@ Available toolset names:
| `ccm` | cost_perspective, cost_breakdown, cost_timeseries, cost_summary, cost_recommendation, cost_anomaly, cost_anomaly_summary, cost_category, cost_account_overview, cost_filter_value, cost_recommendation_stats, cost_recommendation_detail, cost_commitment |
| `sei` | sei_metric, sei_productivity_metric, sei_dora_metric, sei_team, sei_team_detail, sei_org_tree, sei_org_tree_detail, sei_business_alignment, sei_ai_usage, sei_ai_adoption, sei_ai_impact, sei_ai_raw_metric |
| `scs` | scs_artifact_source, artifact_security, scs_artifact_component, scs_artifact_remediation, scs_chain_of_custody, scs_compliance_result, code_repo_security, scs_sbom |
| `sto` | security_issue, security_issue_filter, security_exemption |
| `sto` | security_issue, security_issue_filter, security_exemption, sast_remediation_diff |
| `dbops` | database_schema, database_instance, database_snapshot_object, database_llm_authoring_pipeline |
| `access_control` | user, user_group, service_account, role, role_assignment, resource_group, permission |
| `governance` | policy, policy_set, policy_evaluation |
Expand Down Expand Up @@ -1837,7 +1838,7 @@ Available toolset names:
+--------v---------+
| Registry | <-- Declarative resource definitions
| 37 Toolsets | (data files, not code)
| 215 Resource Types|
| 216 Resource Types|
+--------+---------+
|
+--------v---------+
Expand Down
58 changes: 58 additions & 0 deletions src/registry/extractors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,64 @@ export const stoExemptionsExtract = (raw: unknown, input?: Record<string, unknow
};
};

/**
* STO SAST remediation DiffOccurrences
* (`GET /sto/api/v2/sast-remediation/diff-occurrences`).
*
* API shape:
* { validationScanId, existingOccurrences: [...], newOccurrences: [...],
* existingCount, newCount, matchedCount }
*
* Flatten existing+new into `items[]` tagged with `_partition` so agents can
* tell still-present vs newly introduced occurrences without two list calls.
* Fingerprint is not on the wire (Diff matching is server-side only).
*/
export const stoSastRemediationDiffExtract = (raw: unknown): unknown => {
if (raw === null || raw === undefined || typeof raw !== "object") return raw;
const r = raw as {
validationScanId?: string;
existingOccurrences?: unknown[];
newOccurrences?: unknown[];
// Legacy keys (pre rename) — keep reading briefly for mixed deploys.
existing?: unknown[];
new?: unknown[];
existingCount?: number;
newCount?: number;
matchedCount?: number;
};
const existingItems = Array.isArray(r.existingOccurrences)
? r.existingOccurrences
: Array.isArray(r.existing)
? r.existing
: [];
const newItems = Array.isArray(r.newOccurrences)
? r.newOccurrences
: Array.isArray(r.new)
? r.new
: [];
const tagged = [
...existingItems.map((it) =>
typeof it === "object" && it !== null ? { ...it, _partition: "existing" } : it,
),
...newItems.map((it) =>
typeof it === "object" && it !== null ? { ...it, _partition: "new" } : it,
),
];
const existingCount =
typeof r.existingCount === "number" ? r.existingCount : existingItems.length;
const newCount = typeof r.newCount === "number" ? r.newCount : newItems.length;
const matchedCount =
typeof r.matchedCount === "number" ? r.matchedCount : existingCount + newCount;
return {
items: tagged,
total: matchedCount,
existing_total: existingCount,
new_total: newCount,
matched_count: matchedCount,
validation_scan_id: r.validationScanId,
};
};

/**
* AI Evals control plane — paginated list: `{ data, page, limit, total_elements }`.
*/
Expand Down
99 changes: 98 additions & 1 deletion src/registry/toolsets/sto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ToolsetDefinition } from "../types.js";
import { passthrough, stoExemptionsExtract } from "../extractors.js";
import { passthrough, stoExemptionsExtract, stoSastRemediationDiffExtract } from "../extractors.js";

/**
* Injects a redirect hint into every security_issue list response.
Expand Down Expand Up @@ -765,5 +765,102 @@ export const stoToolset: ToolsetDefinition = {
},
},
},

// ── SAST Remediation Diff (validation vs original scan) ───────────
{
resourceType: "sast_remediation_diff",
displayName: "SAST Remediation Diff",
description:
"Diff validation-scan occurrences against the original SAST scan's ignore set (STO DiffOccurrences). "
+ "Requires BOTH `scan_id` (original scan) and `validation_execution_id` (validation pipeline execution). "
+ "Removes ignored fingerprints from the validation scan (server-side), then splits remaining occurrences into "
+ "`existing` (still present from original in-scope) and `new` (introduced by the remediations). "
+ "Response flattens both partitions into `items[]` tagged with `_partition`. "
+ "Fingerprint is not returned on items — matching stays inside STO Core.",
searchAliases: [
"sast rem diff",
"validation scan diff",
"sast occurrence diff",
"agent remediation validation",
],
relatedResources: [
{
resourceType: "pipeline_security_issue",
relationship: "sibling",
description: "Per-execution Pipeline Security issue view (existing/new partitions for a run).",
},
{
resourceType: "security_issue",
relationship: "sibling",
description: "Cross-execution Issues page listing.",
},
],
toolset: "sto",
scope: "project",
scopeParams: STO_SCOPE,
identifierFields: [],
listFilterFields: [
{ name: "scan_id", description: "REQUIRED. Original STO scan id the agent remediated.", required: true },
{
name: "validation_execution_id",
description:
"REQUIRED (or legacy alias execution_id). Validation pipeline execution id "
+ "(sto-core query param validationExecutionId).",
required: false,
},
{
name: "execution_id",
description:
"Legacy alias for validation_execution_id. Prefer validation_execution_id.",
required: false,
},
{ name: "only_true_positive", type: "boolean", description: "When true (default), only TRUE_POSITIVE triage verdicts are treated as in-scope on the original scan." },
{ name: "limit", type: "number", description: "Max occurrences to return (1–10000, default 1000)." },
{ name: "severity_codes", description: "Comma-separated severities.", enum: ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"] },
{ name: "exclude_repo_patterns", description: "Comma-separated glob patterns matching repository target.name to exclude." },
],
operations: {
list: {
method: "GET",
path: "/sto/api/v2/sast-remediation/diff-occurrences",
operationPolicy: { risk: "read", retryPolicy: "safe" },
queryParams: {
scan_id: "scanId",
validation_execution_id: "validationExecutionId",
// Legacy alias — prefer validation_execution_id.
execution_id: "validationExecutionId",
only_true_positive: "onlyTruePositive",
limit: "limit",
severity_codes: "severityCodes",
exclude_repo_patterns: "excludeRepoPatterns",
},
preflight: async ({ input }) => {
if (typeof input.scan_id !== "string" || input.scan_id.length === 0) {
throw new Error(
"sast_remediation_diff: 'scan_id' is required (original STO scan id).",
);
}
const validationExecutionId =
(typeof input.validation_execution_id === "string" && input.validation_execution_id)
|| (typeof input.execution_id === "string" && input.execution_id)
|| "";
if (!validationExecutionId) {
throw new Error(
"sast_remediation_diff: 'validation_execution_id' is required "
+ "(validation pipeline execution id; alias: execution_id).",
);
}
// Normalize so queryParams maps a single canonical key.
input.validation_execution_id = validationExecutionId;
delete input.execution_id;
},
responseExtractor: stoSastRemediationDiffExtract,
skipCompact: true,
description:
"Diff validation-scan occurrences vs original scan ignore set. Requires scan_id + validation_execution_id. "
+ "Flattens existingOccurrences + newOccurrences into items[]; each item tagged with _partition.",
},
},
},
],
};
Loading