@topline-build/harness-mcp 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,11 +4,11 @@ All notable customer-visible Build MCP changes are recorded here. A source chang
4
4
 
5
5
  ## Unreleased
6
6
 
7
- - Generate a versioned runtime reference and machine-readable documentation manifest from the registry used by MCP `tools/list`.
8
- - Enforce documentation freshness in tests, continuous integration, and release validation.
9
- - Define documentation ownership, pull request impact review, release synchronization, and recurring maintenance.
10
- - Notify the website of an exact npm release immediately, with daily recovery and a review-only pull request boundary.
11
- - Add machine-readable Codex, Claude Code, and Cursor certification with distinct CLI, protocol, client-mediated read, structured failure, and reviewed apply evidence.
7
+ ## 0.5.1 - 2026-09-18
8
+
9
+ - Harden the bundled Claude certification script with strict tool isolation, effective-runtime proof, and noninteractive JSONL validation.
10
+ - Update certification setup documentation and regression coverage.
11
+ - Harden live certification CI and add an exact-version website documentation dispatch probe.
12
12
 
13
13
  ## 0.5.0
14
14
 
package/README.md CHANGED
@@ -75,9 +75,11 @@ can consume provider model quota and runs only for clients whose CLI can reject
75
75
  user/global MCP configuration and disable every built-in tool. Claude Code is
76
76
  currently the supported mediated client; Codex and Cursor report
77
77
  `strict_client_tool_isolation_unavailable` instead of starting an agent with
78
- broader access. The Claude process uses strict MCP configuration, safe mode,
78
+ broader access. The Claude process uses strict MCP configuration, bare mode,
79
79
  no built-in tools, an allowlisted environment, an isolated OAuth cache, and a
80
- server exposing only `platform_inspect_artifact_app`. The write credential is
80
+ server exposing only `platform_inspect_artifact_app`. Its bare noninteractive
81
+ permission bypass is accepted only when Claude's structured init event proves
82
+ that this is the sole effective tool and MCP server. The write credential is
81
83
  available only to the direct exact-SHA apply path. Missing client CLIs, provider
82
84
  login, URL/app inputs, or credentials produce explicit `skipped` results and
83
85
  exit successfully by default; `--require-all` exits 2 for any skip. A failed
package/dist/index.d.ts CHANGED
@@ -32,7 +32,7 @@ export interface PackedArtifactAppBundle {
32
32
  type ToolProgressReporter = (progress: number, message: string) => void | Promise<void>;
33
33
  export declare const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
34
34
  export declare const BUILD_MCP_SERVER_NAME = "topline-build-stdio";
35
- export declare const HARNESS_MCP_VERSION = "0.5.0";
35
+ export declare const HARNESS_MCP_VERSION = "0.5.1";
36
36
  export declare const DEFAULT_OAUTH_SCOPES: readonly string[];
37
37
  export interface BuildMcpSetupOption {
38
38
  readonly flag: string;
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } fr
13
13
  export const DEFAULT_MCP_PROTOCOL_VERSION = "2025-11-25";
14
14
  const DEFAULT_TIMEOUT_MS = 120_000;
15
15
  export const BUILD_MCP_SERVER_NAME = "topline-build-stdio";
16
- export const HARNESS_MCP_VERSION = "0.5.0";
16
+ export const HARNESS_MCP_VERSION = "0.5.1";
17
17
  export const DEFAULT_OAUTH_SCOPES = Object.freeze(["harness:artifact:read", "harness:artifact:write", "harness:artifact:deploy", "harness:artifact:delete"]);
18
18
  export const BUILD_MCP_SETUP_OPTIONS = Object.freeze([
19
19
  { flag: "--url", env: "TOPLINE_API_URL", aliases: ["PLATFORM_API_URL", "OVERSITE_API_URL"], required: true, description: "Topline tenant base URL. HTTPS is required except for loopback development." },
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "package": {
4
4
  "name": "@topline-build/harness-mcp",
5
- "version": "0.5.0",
5
+ "version": "0.5.1",
6
6
  "bin": {
7
7
  "topline-mcp": "dist/cli.js"
8
8
  }
@@ -12,7 +12,7 @@
12
12
  "defaultVersion": "2025-11-25",
13
13
  "server": {
14
14
  "name": "topline-build-stdio",
15
- "version": "0.5.0"
15
+ "version": "0.5.1"
16
16
  }
17
17
  },
18
18
  "generatedFrom": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Generated from `src/index.ts#BUILD_MCP_TOOLS`. Do not edit this file by hand.
4
4
 
5
- Package: `@topline-build/harness-mcp@0.5.0`
5
+ Package: `@topline-build/harness-mcp@0.5.1`
6
6
  Transport: `stdio`
7
7
  Default protocol: `2025-11-25`
8
8
  Canonical guide: https://topline.build/docs/build-mcp-guide
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topline-build/harness-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Topline Build MCP for creating and deploying artifact app drafts.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,6 +20,23 @@ const EXPECTED_TOOLS = [
20
20
  "platform_validate_artifact",
21
21
  "platform_plan_artifact_draft_delete"
22
22
  ];
23
+ const PROVIDER_ENV_KEYS = [
24
+ "ANTHROPIC_API_KEY",
25
+ "TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY",
26
+ "TOPLINE_CERTIFICATION_ALLOW_BEDROCK",
27
+ "CLAUDE_CODE_USE_BEDROCK",
28
+ "ANTHROPIC_MODEL",
29
+ "ANTHROPIC_SMALL_FAST_MODEL",
30
+ "DISABLE_PROMPT_CACHING",
31
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
32
+ "AWS_ACCESS_KEY_ID",
33
+ "AWS_SECRET_ACCESS_KEY",
34
+ "AWS_SESSION_TOKEN",
35
+ "AWS_REGION",
36
+ "AWS_DEFAULT_REGION",
37
+ "AWS_EC2_METADATA_DISABLED",
38
+ "AWS_STS_REGIONAL_ENDPOINTS"
39
+ ];
23
40
  const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
24
41
  const manifest = JSON.parse(await readFile(new URL("../docs/generated/build-mcp-manifest.json", import.meta.url), "utf8"));
25
42
  let args;
@@ -38,8 +55,7 @@ if (process.argv[2] === "--certification-read-only-server") {
38
55
  }
39
56
 
40
57
  async function runCertificationReadOnlyServer(values) {
41
- delete process.env.ANTHROPIC_API_KEY;
42
- delete process.env.TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY;
58
+ for (const key of PROVIDER_ENV_KEYS) delete process.env[key];
43
59
  const opts = parseHarnessOptions(values, withoutManualToken(process.env));
44
60
  const server = createHarnessMcpStdioServer(opts, ["platform_inspect_artifact_app"]);
45
61
  await server.connect(new StdioServerTransport());
@@ -179,10 +195,7 @@ async function agentMediatedRead(name, command, scratch, apiUrl, appDir, workspa
179
195
  if (!appDir) return skipped("certification_app_missing");
180
196
  const server = {
181
197
  command: process.execPath,
182
- env: {
183
- ANTHROPIC_API_KEY: "",
184
- TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY: ""
185
- },
198
+ env: Object.fromEntries(PROVIDER_ENV_KEYS.map((key) => [key, ""])),
186
199
  args: [
187
200
  fileURLToPath(import.meta.url),
188
201
  "--certification-read-only-server",
@@ -198,11 +211,11 @@ async function agentMediatedRead(name, command, scratch, apiUrl, appDir, workspa
198
211
  const configPath = path.join(configDir, "mcp.json");
199
212
  await writeFile(configPath, JSON.stringify({ mcpServers: { "topline-build": server } }, null, 2), "utf8");
200
213
  const result = run(command, [
201
- "--mcp-config", configPath, "--strict-mcp-config", "--safe-mode",
214
+ "--mcp-config", configPath, "--strict-mcp-config", "--bare",
202
215
  "--tools", "", "--allowedTools", "mcp__topline-build__platform_inspect_artifact_app",
203
- "--permission-mode", "dontAsk", "--no-chrome", "--disable-slash-commands",
216
+ "--dangerously-skip-permissions", "--no-chrome", "--disable-slash-commands",
204
217
  "--no-session-persistence", "--max-budget-usd", "0.25",
205
- "-p", "--output-format", "stream-json", prompt
218
+ "-p", "--verbose", "--output-format", "stream-json", prompt
206
219
  ], scratch, 180000, strictAgentEnvironment(process.env));
207
220
  if (!result.ok) {
208
221
  return authUnavailable(result.output)
@@ -211,8 +224,9 @@ async function agentMediatedRead(name, command, scratch, apiUrl, appDir, workspa
211
224
  }
212
225
  const usedTool = hasToolCallEvidence(result.output, "platform_inspect_artifact_app");
213
226
  const certified = hasAssistantCertificationEvidence(result.output);
214
- return usedTool && certified
215
- ? passed({ tool: "platform_inspect_artifact_app", providerModelUsed: true })
227
+ const isolated = hasStrictClaudeInitEvidence(result.output);
228
+ return usedTool && certified && isolated
229
+ ? passed({ tool: "platform_inspect_artifact_app", providerModelUsed: true, strictToolIsolation: true, provider: process.env.CLAUDE_CODE_USE_BEDROCK === "1" ? "bedrock" : "anthropic" })
216
230
  : failed("client_mediated_tool_evidence_missing", result.exitCode);
217
231
  }
218
232
 
@@ -355,6 +369,18 @@ function selfTestReport() {
355
369
  ANTHROPIC_API_KEY: "anthropic-secret",
356
370
  TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY: "1"
357
371
  });
372
+ const bedrockStrictEnvironment = strictAgentEnvironment({
373
+ PATH: "kept-path",
374
+ AWS_ACCESS_KEY_ID: "temporary-access-key",
375
+ AWS_SECRET_ACCESS_KEY: "temporary-secret-key",
376
+ AWS_SESSION_TOKEN: "temporary-session-token",
377
+ AWS_REGION: "us-west-2",
378
+ AWS_DEFAULT_REGION: "us-west-2",
379
+ CLAUDE_CODE_USE_BEDROCK: "1",
380
+ ANTHROPIC_MODEL: "global.anthropic.claude-sonnet-4-6",
381
+ ANTHROPIC_SMALL_FAST_MODEL: "global.anthropic.claude-sonnet-4-6",
382
+ TOPLINE_CERTIFICATION_ALLOW_BEDROCK: "1"
383
+ });
358
384
  if (hasToolCallEvidence(promptOnly, toolName)
359
385
  || !hasToolCallEvidence(toolEvent, toolName)
360
386
  || hasAssistantCertificationEvidence(promptOnly)
@@ -367,7 +393,12 @@ function selfTestReport() {
367
393
  || strictEnvironment.USERPROFILE !== "kept-profile"
368
394
  || ["AWS_ACCESS_KEY_ID", "DATABASE_URL", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "CURSOR_API_KEY", "TOPLINE_MCP_TOKEN"].some((key) => key in strictEnvironment)
369
395
  || optedInStrictEnvironment.ANTHROPIC_API_KEY !== "anthropic-secret"
370
- || "TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY" in optedInStrictEnvironment) {
396
+ || "TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY" in optedInStrictEnvironment
397
+ || bedrockStrictEnvironment.CLAUDE_CODE_USE_BEDROCK !== "1"
398
+ || bedrockStrictEnvironment.AWS_ACCESS_KEY_ID !== "temporary-access-key"
399
+ || bedrockStrictEnvironment.AWS_SECRET_ACCESS_KEY !== "temporary-secret-key"
400
+ || bedrockStrictEnvironment.AWS_SESSION_TOKEN !== "temporary-session-token"
401
+ || "TOPLINE_CERTIFICATION_ALLOW_BEDROCK" in bedrockStrictEnvironment) {
371
402
  throw new Error("client_tool_evidence_self_test_failed");
372
403
  }
373
404
  return {
@@ -435,7 +466,7 @@ function clientCommand(name) {
435
466
  return { executable, prefix: [] };
436
467
  }
437
468
 
438
- function run(command, commandArgs, cwd, timeout = 30000, env = withoutManualToken(process.env)) {
469
+ function run(command, commandArgs, cwd, timeout = 30000, env = clientProbeEnvironment(process.env)) {
439
470
  const result = spawnSync(command.executable, [...command.prefix, ...commandArgs], {
440
471
  cwd,
441
472
  encoding: "utf8",
@@ -453,7 +484,7 @@ function run(command, commandArgs, cwd, timeout = 30000, env = withoutManualToke
453
484
  }
454
485
 
455
486
  function authUnavailable(output) {
456
- return /not logged in|not authenticated|authentication required|api key|login required|please login|sign in/i.test(output);
487
+ return /not logged in|not authenticated|authentication required|api key|login required|please login|sign in|unable to locate credentials|security token|AccessDeniedException|UnrecognizedClientException/i.test(output);
457
488
  }
458
489
 
459
490
  function hasToolCallEvidence(output, toolName) {
@@ -468,6 +499,29 @@ function hasToolCallEvidence(output, toolName) {
468
499
  return false;
469
500
  }
470
501
 
502
+ function hasStrictClaudeInitEvidence(output) {
503
+ for (const line of output.split(/\r?\n/)) {
504
+ if (!line.trim().startsWith("{")) continue;
505
+ try {
506
+ const value = JSON.parse(line);
507
+ if (value?.type !== "system" || value?.subtype !== "init") continue;
508
+ return Array.isArray(value.tools)
509
+ && value.tools.length === 1
510
+ && value.tools[0] === "mcp__topline-build__platform_inspect_artifact_app"
511
+ && Array.isArray(value.mcp_servers)
512
+ && value.mcp_servers.length === 1
513
+ && value.mcp_servers[0]?.name === "topline-build"
514
+ && value.mcp_servers[0]?.status === "connected"
515
+ && value.permissionMode === "bypassPermissions"
516
+ && Array.isArray(value.slash_commands)
517
+ && value.slash_commands.length === 0;
518
+ } catch {
519
+ // Only the structured Claude init event can prove the effective tool set.
520
+ }
521
+ }
522
+ return false;
523
+ }
524
+
471
525
  function containsToolCall(value, toolName) {
472
526
  if (!value || typeof value !== "object") return false;
473
527
  const type = typeof value.type === "string" ? value.type : "";
@@ -510,17 +564,34 @@ function withoutManualToken(source) {
510
564
  }
511
565
 
512
566
  function strictAgentEnvironment(source) {
567
+ const env = clientProbeEnvironment(source);
568
+ if (source.TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY === "1" && source.ANTHROPIC_API_KEY) {
569
+ env.ANTHROPIC_API_KEY = source.ANTHROPIC_API_KEY;
570
+ }
571
+ const bedrockCredentialKeys = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"];
572
+ if (source.TOPLINE_CERTIFICATION_ALLOW_BEDROCK === "1"
573
+ && source.CLAUDE_CODE_USE_BEDROCK === "1"
574
+ && bedrockCredentialKeys.every((key) => typeof source[key] === "string" && source[key].length > 0)) {
575
+ for (const key of [
576
+ ...bedrockCredentialKeys,
577
+ "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_EC2_METADATA_DISABLED", "AWS_STS_REGIONAL_ENDPOINTS",
578
+ "CLAUDE_CODE_USE_BEDROCK", "ANTHROPIC_MODEL", "ANTHROPIC_SMALL_FAST_MODEL",
579
+ "DISABLE_PROMPT_CACHING", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"
580
+ ]) {
581
+ if (typeof source[key] === "string" && source[key].length > 0) env[key] = source[key];
582
+ }
583
+ }
584
+ return env;
585
+ }
586
+
587
+ function clientProbeEnvironment(source) {
513
588
  const allowed = new Set([
514
589
  "ALL_PROXY", "APPDATA", "CI", "COMSPEC", "FORCE_COLOR", "HTTP_PROXY", "HTTPS_PROXY",
515
590
  "LANG", "LC_ALL", "LOCALAPPDATA", "NODE_EXTRA_CA_CERTS", "NO_COLOR", "NO_PROXY", "PATH",
516
591
  "PATHEXT", "PROGRAMDATA", "SSL_CERT_DIR", "SSL_CERT_FILE", "SYSTEMROOT", "TEMP", "TMP",
517
592
  "USERPROFILE", "WINDIR"
518
593
  ]);
519
- const env = Object.fromEntries(Object.entries(source).filter(([key]) => allowed.has(key.toUpperCase())));
520
- if (source.TOPLINE_CERTIFICATION_ALLOW_ANTHROPIC_API_KEY === "1" && source.ANTHROPIC_API_KEY) {
521
- env.ANTHROPIC_API_KEY = source.ANTHROPIC_API_KEY;
522
- }
523
- return env;
594
+ return Object.fromEntries(Object.entries(source).filter(([key]) => allowed.has(key.toUpperCase())));
524
595
  }
525
596
 
526
597
  function structured(result) {