@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 +5 -5
- package/README.md +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/generated/build-mcp-manifest.json +2 -2
- package/docs/generated/build-mcp-reference.md +1 -1
- package/package.json +1 -1
- package/scripts/certify-live.mjs +90 -19
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
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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,
|
|
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`.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
package/scripts/certify-live.mjs
CHANGED
|
@@ -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
|
|
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", "--
|
|
214
|
+
"--mcp-config", configPath, "--strict-mcp-config", "--bare",
|
|
202
215
|
"--tools", "", "--allowedTools", "mcp__topline-build__platform_inspect_artifact_app",
|
|
203
|
-
"--
|
|
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
|
-
|
|
215
|
-
|
|
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 =
|
|
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
|
-
|
|
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) {
|