Skip to content
Closed
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
8 changes: 4 additions & 4 deletions docs/coding-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ Files in `src/registry/toolsets/*.ts` must export a `ToolsetDefinition` object (

They must NOT:

- Import `HarnessClient`, `McpServer`, or `Registry`
- Import `HarnessClient`, `McpServer`, `Registry`, or `createLogger`
- Make HTTP calls directly (use `preflight` hooks with structural client interfaces from `PreflightContext` when needed)
- Contain business logic beyond field mapping
- Use `console.log()` (or any stdout writes)
- Use `console.log()` or any logging (stdout corrupts stdio JSON-RPC; toolsets must stay silent)

### 3. Use Shared Response Extractors

Expand Down Expand Up @@ -257,7 +257,7 @@ pnpm inspect
Before every commit, verify:

- [ ] **No new `server.registerTool()` calls** — only toolset definitions added/modified
- [ ] **No `console.log()`** — only `createLogger()` for stderr output
- [ ] **No `console.log()` or `createLogger()` in toolsets** — logging belongs in tool handlers only
- [ ] **No direct `fetch()` calls** — all Harness API HTTP goes through `HarnessClient` (exceptions: log blob CDN URLs, audit webhooks — see rule 10)
- [ ] **No new `harness-*.ts` handler files** — the 11 handlers are fixed
- [ ] **Toolset files are pure data** — no imports of `HarnessClient`, `McpServer`, or `Registry`
Expand All @@ -281,7 +281,7 @@ Before every commit, verify:
|-------------|---------------------|
| Add a new `server.registerTool()` call | Breaks the consolidated tool model — use the registry instead |
| Import `HarnessClient` in a toolset file | Toolsets are pure data — they don't execute HTTP directly |
| Use `console.log()` | Corrupts stdio JSON-RPC transport |
| Use `console.log()` or `createLogger()` in toolsets | Corrupts stdio JSON-RPC / violates pure-data toolset model |
| Make raw `fetch()` calls | Bypasses auth, retry, rate limiting |
| Create a new HTTP client instance | Singleton pattern — one client, injected everywhere |
| Hardcode `accountIdentifier` | Client injects it automatically |
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": "0.3",
"name": "harness-mcp-server",
"display_name": "Harness",
"version": "3.2.5",
"version": "3.2.6",
"description": "Connect AI agents to Harness for CI/CD, code repositories, pull requests, services, environments, and feature flags.",
"long_description": "The Harness MCP Server gives MCP-compatible clients access to Harness platform workflows through consolidated tools, resources, and prompts. It supports local stdio usage and can be bundled as a Node-based MCPB extension.",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion mcp-directory/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": "0.3",
"name": "harness-mcp-server",
"display_name": "Harness",
"version": "3.2.5",
"version": "3.2.6",
"description": "Connect AI agents to Harness for CI/CD, code repositories, pull requests, services, environments, and feature flags.",
"long_description": "The Harness MCP Server gives MCP-compatible clients access to Harness platform workflows through consolidated tools, resources, and prompts. It supports local stdio usage and can be bundled as a Node-based MCPB extension.",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "harness-mcp-v2",
"version": "3.2.5",
"version": "3.2.6",
"description": "Give AI agents full access to the Harness.io platform — manage pipelines, deployments, cloud costs, chaos engineering, feature flags, SEI, and 125+ resource types through 11 MCP tools",
"type": "module",
"main": "build/index.js",
Expand Down
24 changes: 7 additions & 17 deletions src/registry/toolsets/scs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { ToolsetDefinition, FilterFieldSpec, ParamsSchema } from "../types.js";
import { scsCleanExtract, scsListExtract } from "../extractors.js";
import { HarnessApiError } from "../../utils/errors.js";
import { createLogger } from "../../utils/logger.js";

const log = createLogger("scs-toolset");

function filterFieldsToParamsSchema(fields: FilterFieldSpec[]): ParamsSchema {
return {
Expand Down Expand Up @@ -961,10 +958,10 @@ export const scsToolset: ToolsetDefinition = {
* • HTTP 4xx ⇒ fail CLOSED (throw). A client-side error (bad args,
* auth, scope) indicates a real problem; proceeding
* would silently bypass the duplicate invariant.
* • HTTP 5xx / network / non-HarnessApiError ⇒ fail OPEN with a
* structured warn log. The check is best-effort and
* transient upstream issues must not permanently block
* legitimate remediation creation.
* • HTTP 5xx / network / non-HarnessApiError ⇒ fail OPEN silently.
* The check is best-effort and transient upstream
* issues must not permanently block legitimate
* remediation creation.
*
* SCOPE: the preflight's inner list call inherits `org_id` /
* `project_id` from the outer create input; it does NOT fall back
Expand Down Expand Up @@ -1041,9 +1038,9 @@ export const scsToolset: ToolsetDefinition = {
// 4xx → fail CLOSED. A client-side error (bad args, auth, scope)
// indicates a real problem — creating through it would
// silently bypass the duplicate invariant.
// 5xx / network / timeout → fail OPEN with a logged warning.
// The check is best-effort; transient upstream issues
// should not permanently block remediation creation.
// 5xx / network / timeout → fail OPEN silently. The check is
// best-effort; transient upstream issues should not
// permanently block remediation creation.
const status = err instanceof HarnessApiError ? err.statusCode : undefined;
if (status !== undefined && status >= 400 && status < 500) {
throw new Error(
Expand All @@ -1052,13 +1049,6 @@ export const scsToolset: ToolsetDefinition = {
+ `Resolve the list error (verify artifact_id and scope) before retrying create.`,
);
}
log.warn("scs_remediation_pr preflight skipped (transient error)", {
artifactId,
purl,
page,
status,
error: err instanceof Error ? err.message : String(err),
});
return;
}
const batch = pickItems(raw);
Expand Down
10 changes: 7 additions & 3 deletions tests/coding-standards/architecture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const FORBIDDEN_TOOLSET_IMPORTS: Array<{ pattern: RegExp; reason: string }> = [
{ pattern: /from\s+["'][^"']*harness-client/, reason: "HarnessClient import" },
{ pattern: /from\s+["']@modelcontextprotocol\/sdk/, reason: "McpServer/MCP SDK import" },
{ pattern: /from\s+["'][^"']*\/registry\/index/, reason: "Registry import" },
{ pattern: /from\s+["'][^"']*\/utils\/logger/, reason: "createLogger import (toolsets are pure data — no logging)" },
];

/** Files allowed to call the global fetch() API (documented exceptions). */
Expand Down Expand Up @@ -209,7 +210,7 @@ describe("Coding standards — logging and HTTP", () => {
expect(violations, `console.log() found in:\n${violations.join("\n")}`).toEqual([]);
});

it("toolset files do not use console.* (use createLogger in handlers, not toolsets)", () => {
it("toolset files do not use console.* or createLogger (handlers only)", () => {
const violations: string[] = [];
const toolsetDir = join(SRC, "registry/toolsets");

Expand All @@ -219,11 +220,14 @@ describe("Coding standards — logging and HTTP", () => {

const content = readFileSync(file, "utf8");
if (/\bconsole\.(log|error|warn|info|debug)\s*\(/.test(content)) {
violations.push(fileRel);
violations.push(`${fileRel}: console.*`);
}
if (/\bcreateLogger\s*\(/.test(content)) {
violations.push(`${fileRel}: createLogger()`);
}
}

expect(violations, `console.* found in toolsets:\n${violations.join("\n")}`).toEqual([]);
expect(violations, `Logging in toolsets (use handlers/registry, not toolsets):\n${violations.join("\n")}`).toEqual([]);
});

it("does not use raw fetch() in tool handlers or toolset definitions", () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/release-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe("release metadata", () => {
const rootManifest = readJson("manifest.json");
const directoryManifest = readJson("mcp-directory/manifest.json");

expect(packageJson.version).toBe("3.2.5");
expect(packageJson.version).toBe("3.2.6");
expect(rootManifest.version).toBe(packageJson.version);
expect(directoryManifest.version).toBe(packageJson.version);
});
Expand Down
Loading