diff --git a/docs/coding-standards.md b/docs/coding-standards.md index 6b621262..07049324 100644 --- a/docs/coding-standards.md +++ b/docs/coding-standards.md @@ -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 @@ -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` @@ -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 | diff --git a/manifest.json b/manifest.json index d5516b2f..0d6b7e3d 100644 --- a/manifest.json +++ b/manifest.json @@ -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": { diff --git a/mcp-directory/manifest.json b/mcp-directory/manifest.json index b176de11..2d79a76c 100644 --- a/mcp-directory/manifest.json +++ b/mcp-directory/manifest.json @@ -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": { diff --git a/package.json b/package.json index c4ec82fd..86d23fcc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/registry/toolsets/scs.ts b/src/registry/toolsets/scs.ts index 022b308f..eb30d0f4 100644 --- a/src/registry/toolsets/scs.ts +++ b/src/registry/toolsets/scs.ts @@ -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 { @@ -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 @@ -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( @@ -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); diff --git a/tests/coding-standards/architecture.test.ts b/tests/coding-standards/architecture.test.ts index da7627cb..bed6fd3c 100644 --- a/tests/coding-standards/architecture.test.ts +++ b/tests/coding-standards/architecture.test.ts @@ -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). */ @@ -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"); @@ -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", () => { diff --git a/tests/release-metadata.test.ts b/tests/release-metadata.test.ts index adab9b71..781a31fa 100644 --- a/tests/release-metadata.test.ts +++ b/tests/release-metadata.test.ts @@ -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); });