@bridge_gpt/mcp-server 0.2.25 → 0.2.27

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.
Files changed (73) hide show
  1. package/README.md +59 -9
  2. package/build/agents.generated.js +1 -1
  3. package/build/bridge-api-urls.js +31 -0
  4. package/build/commands.generated.js +4 -4
  5. package/build/conductor-bin.js +68 -9267
  6. package/build/conductor-bundle-artifacts.js +802 -0
  7. package/build/conductor-bundle-cli.js +256 -0
  8. package/build/conductor-claude-hook-bin.js +2 -172
  9. package/build/decision-page-template.js +2 -31
  10. package/build/docs.generated.js +2 -1
  11. package/build/doctor.js +148 -1
  12. package/build/env-flags.js +31 -0
  13. package/build/index.js +4659 -34836
  14. package/build/init.js +87 -59
  15. package/build/install-bridge.js +380 -22
  16. package/build/mcp-host-config.js +521 -0
  17. package/build/mcp-host-targets.js +194 -0
  18. package/build/mcp-install-state.js +175 -0
  19. package/build/pipelines.generated.js +5 -4
  20. package/build/readme.generated.js +1 -1
  21. package/build/start-tickets.js +118 -5
  22. package/build/tool-surface-gating.js +396 -0
  23. package/build/version.generated.js +1 -1
  24. package/docs/install/mcp-tool-integrations.md +2 -2
  25. package/package.json +5 -7
  26. package/design-assets/favicon/android-chrome-192x192.png +0 -0
  27. package/design-assets/favicon/android-chrome-512x512.png +0 -0
  28. package/design-assets/favicon/apple-touch-icon.png +0 -0
  29. package/design-assets/favicon/favicon-16x16.png +0 -0
  30. package/design-assets/favicon/favicon.ico +0 -0
  31. package/design-assets/favicon/site.webmanifest +0 -1
  32. package/public/css/main.min.css +0 -4971
  33. package/public/css/main.min.css.map +0 -1
  34. package/public/fonts/OFL.txt +0 -93
  35. package/public/fonts/SourceSansPro-Black.ttf +0 -0
  36. package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  37. package/public/fonts/SourceSansPro-Bold.ttf +0 -0
  38. package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  39. package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  40. package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  41. package/public/fonts/SourceSansPro-Italic.ttf +0 -0
  42. package/public/fonts/SourceSansPro-Light.ttf +0 -0
  43. package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
  44. package/public/fonts/SourceSansPro-Regular.ttf +0 -0
  45. package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
  46. package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  47. package/public/img/bridge-logo-160x51.webp +0 -0
  48. package/public/img/bridge-logo-300x92.webp +0 -0
  49. package/public/img/favicon/android-chrome-192x192.png +0 -0
  50. package/public/img/favicon/android-chrome-512x512.png +0 -0
  51. package/public/img/favicon/apple-touch-icon.png +0 -0
  52. package/public/img/favicon/favicon-16x16.png +0 -0
  53. package/public/img/favicon/favicon-32x32.png +0 -0
  54. package/public/img/favicon/favicon.ico +0 -0
  55. package/public/img/favicon/site.webmanifest +0 -1
  56. package/public/img/installation/bitbucket/app-password-1.png +0 -0
  57. package/public/img/installation/bitbucket/app-password-2.png +0 -0
  58. package/public/img/installation/bitbucket/create-token-1.png +0 -0
  59. package/public/img/installation/bitbucket/create-token-2.png +0 -0
  60. package/public/img/installation/bitbucket/webhook-1.png +0 -0
  61. package/public/img/installation/github/github-review-webhook.png +0 -0
  62. package/public/img/installation/jira/credentials/api-key.png +0 -0
  63. package/public/img/installation/jira/webhook/create-rule.png +0 -0
  64. package/public/img/installation/jira/webhook/project-settings.png +0 -0
  65. package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
  66. package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
  67. package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
  68. package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
  69. package/public/img/installation/pinecone/pinecone-index.png +0 -0
  70. package/public/js/main.min.js +0 -10427
  71. package/public/js/main.min.js.map +0 -1
  72. package/smoke-test/SMOKE-TEST.md +0 -560
  73. package/smoke-test/smoke-test-mcp.md +0 -23
package/build/doctor.js CHANGED
@@ -20,7 +20,11 @@ import os from "os";
20
20
  import path from "path";
21
21
  import { createDefaultStartTicketsDeps } from "./start-tickets.js";
22
22
  import { VERSION } from "./version.generated.js";
23
- import { collectInstallStatusChecks, formatInstallStatusReport, } from "./install-doctor.js";
23
+ import { collectInstallStatusChecks, formatInstallStatusReport, resolveInstallDoctorTarget, } from "./install-doctor.js";
24
+ import { parseDefaultOnEnvFlag } from "./env-flags.js";
25
+ import { createBridgeApiUrls } from "./bridge-api-urls.js";
26
+ import { probeToolSurface } from "./tool-surface-gating.js";
27
+ import { resolveBapiCredentials } from "./credential-store.js";
24
28
  import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
25
29
  import { getDoctorPrereqDescriptors, probePrerequisite, } from "./start-tickets-prereqs.js";
26
30
  import { resolveProfiles } from "./mcp-profile.js";
@@ -51,6 +55,14 @@ export function getDoctorUsage() {
51
55
  "bootstrap-field completeness, and repository-indexing state. It performs",
52
56
  "read-only GETs only and never affects the exit code.",
53
57
  "",
58
+ "It also includes an advisory 'MCP tool surface' section (BAPI-641): what",
59
+ "dynamic capability gating would advertise for this repo. It performs at most",
60
+ "one read-only GET to /jira/mcp/tool-surface (none under the kill switch) and",
61
+ "is advisory/fail-open — a timeout or malformed response is reported as",
62
+ "'fail-open to full surface' and never affects the exit code. Clients that",
63
+ "ignore notifications/tools/list_changed must reconnect or start a new MCP",
64
+ "session to observe surface changes; no project MCP config change is required.",
65
+ "",
54
66
  "Conductor ledger / native-module diagnostics (the SQLite ledger's native",
55
67
  "binding load status and Node-version skew) live under a separate command:",
56
68
  " conductor doctor",
@@ -363,6 +375,111 @@ export function formatLauncherCacheReport(inspections) {
363
375
  }
364
376
  return lines.join("\n");
365
377
  }
378
+ /**
379
+ * Collect the read-only tool-surface diagnostic. Evaluates the kill switch first
380
+ * and returns without any network request when disabled. For an enabled flag and
381
+ * a resolved repo/credential, performs ONE 500 ms `probeToolSurface()` GET to the
382
+ * deployed route. Credential values live ONLY in the request headers and never in
383
+ * the returned result. Never throws.
384
+ */
385
+ export async function collectToolSurfaceDiagnostic(deps) {
386
+ const enabled = parseDefaultOnEnvFlag(deps.env.BAPI_MCP_TOOL_SURFACE_GATING_ENABLED);
387
+ if (!enabled) {
388
+ return { enabled: false, reason: "kill-switch" };
389
+ }
390
+ const target = await resolveInstallDoctorTarget(deps);
391
+ if (!target.repoName) {
392
+ return {
393
+ enabled: true,
394
+ reason: "unresolved",
395
+ detail: "no BAPI_REPO_NAME in the environment or project-local MCP configs",
396
+ };
397
+ }
398
+ const credDeps = {
399
+ env: deps.env,
400
+ homedir: deps.homedir,
401
+ platform: deps.platform,
402
+ readFile: deps.readFile,
403
+ stat: deps.stat,
404
+ stderr: () => { },
405
+ };
406
+ const cred = await resolveBapiCredentials(target.repoName, credDeps);
407
+ if (!cred.ok) {
408
+ return {
409
+ enabled: true,
410
+ reason: "unresolved",
411
+ detail: `no Bridge API credential resolved (${cred.kind})`,
412
+ };
413
+ }
414
+ const apiKey = cred.credentials.apiKey;
415
+ const urls = createBridgeApiUrls(target.baseUrl);
416
+ const url = urls.buildGetUrl("/mcp/tool-surface", {
417
+ repo_name: target.repoName,
418
+ });
419
+ const result = await probeToolSurface({
420
+ url,
421
+ resolveHeaders: async () => ({ "X-API-Key": apiKey }),
422
+ fetchFn: deps.fetch,
423
+ });
424
+ if (result.reason === "blocked") {
425
+ return {
426
+ enabled: true,
427
+ reason: "blocked",
428
+ blockedTools: Array.from(result.blockedTools),
429
+ catalogRevision: result.catalogRevision,
430
+ evaluatedToolCount: result.evaluatedToolCount,
431
+ };
432
+ }
433
+ if (result.reason === "timeout") {
434
+ return { enabled: true, reason: "timeout" };
435
+ }
436
+ return { enabled: true, reason: "malformed", subtype: result.subtype };
437
+ }
438
+ /**
439
+ * Render the advisory "MCP tool surface" section (pure formatting — no probing).
440
+ * States the kill-switch state, probe reachability, decision reason/subtype,
441
+ * fail-open behavior when applicable, blocked IDs/count/revision for a valid
442
+ * response, and clarifies that blocked IDs are intersected with the locally
443
+ * active profile and SDK-enabled baseline only when an MCP session starts.
444
+ */
445
+ export function formatToolSurfaceDiagnosticReport(diag) {
446
+ const lines = ["", "MCP tool surface (dynamic capability gating — advisory)", ""];
447
+ lines.push(`Kill switch: ${diag.enabled ? "ENABLED (gating active)" : "DISABLED (full surface)"}`);
448
+ switch (diag.reason) {
449
+ case "kill-switch":
450
+ lines.push("Reason: kill-switch — BAPI_MCP_TOOL_SURFACE_GATING_ENABLED is off, so the full profile surface is advertised and no probe is performed.");
451
+ break;
452
+ case "unresolved":
453
+ lines.push(`Reason: unresolved — ${diag.detail ?? "repo/credential not resolved"}; the probe was skipped and the full surface is advertised (fail-open to full surface).`);
454
+ break;
455
+ case "blocked": {
456
+ const ids = diag.blockedTools ?? [];
457
+ lines.push("Reason: blocked — the backend returned a valid capability decision.");
458
+ lines.push(`Probe: reachable (HTTP 200, valid response).`);
459
+ lines.push(`Blocked tools (${ids.length}): [${ids.join(", ")}]`);
460
+ if (diag.catalogRevision)
461
+ lines.push(`Catalog revision: ${diag.catalogRevision}`);
462
+ if (typeof diag.evaluatedToolCount === "number") {
463
+ lines.push(`Evaluated tool count: ${diag.evaluatedToolCount}`);
464
+ }
465
+ break;
466
+ }
467
+ case "timeout":
468
+ lines.push("Reason: timeout — the 500 ms probe deadline elapsed; fail-open to full surface.");
469
+ break;
470
+ case "malformed":
471
+ lines.push(`Reason: malformed (${diag.subtype ?? "unknown"}) — fail-open to full surface.`);
472
+ break;
473
+ }
474
+ lines.push("");
475
+ lines.push("Blocked IDs are intersected with the locally active MCP profile and the current SDK-enabled");
476
+ lines.push("baseline only when an MCP session starts, so IDs unknown to this package or excluded by the");
477
+ lines.push("active profile have no effect. Capability-hidden tools remain registered and callable — the");
478
+ lines.push("backend is the enforcement boundary. This section is advisory and never changes the exit code.");
479
+ lines.push("Clients that ignore notifications/tools/list_changed must reconnect or start a new MCP session");
480
+ lines.push("to observe surface changes; no project MCP configuration change is required.");
481
+ return lines.join("\n");
482
+ }
366
483
  /**
367
484
  * CLI entry for the read-only `doctor` subcommand. Returns a process exit code.
368
485
  * Help returns 0; parser errors return 1; otherwise it prints the report and
@@ -429,6 +546,36 @@ export async function runDoctorCli(argv, overrides = {}) {
429
546
  /* install-status diagnostics are advisory; never block the doctor report */
430
547
  }
431
548
  }
549
+ // Advisory MCP tool-surface capability section (BAPI-641). Strictly read-only:
550
+ // one 500 ms GET (or none, under the kill switch). Any timeout, malformed
551
+ // response, or unexpected throw degrades to a "fail-open to full surface" line
552
+ // and never affects the exit code, exactly like install-status above.
553
+ if (overrides.toolSurface !== false) {
554
+ try {
555
+ const injectedFs = deps;
556
+ const toolSurfaceDeps = {
557
+ env: overrides.toolSurface?.env ?? deps.env,
558
+ cwd: overrides.toolSurface?.cwd ?? deps.cwd,
559
+ platform: overrides.toolSurface?.platform ?? deps.platform,
560
+ homedir: overrides.toolSurface?.homedir ?? injectedFs.homedir ?? os.homedir,
561
+ readFile: overrides.toolSurface?.readFile ??
562
+ injectedFs.readFile ??
563
+ ((p) => readFile(p, "utf-8")),
564
+ stat: overrides.toolSurface?.stat ?? injectedFs.stat ?? ((p) => stat(p)),
565
+ fetch: overrides.toolSurface?.fetch ?? ((...args) => fetch(...args)),
566
+ };
567
+ const diagnostic = await collectToolSurfaceDiagnostic(toolSurfaceDeps);
568
+ log(formatToolSurfaceDiagnosticReport(diagnostic));
569
+ }
570
+ catch {
571
+ // Any unexpected failure still degrades to a sanitized advisory section.
572
+ log(formatToolSurfaceDiagnosticReport({
573
+ enabled: parseDefaultOnEnvFlag(deps.env.BAPI_MCP_TOOL_SURFACE_GATING_ENABLED),
574
+ reason: "malformed",
575
+ subtype: "unexpected",
576
+ }));
577
+ }
578
+ }
432
579
  if (!collection.ok)
433
580
  return 1;
434
581
  return collection.results.some((r) => !r.found) ? 1 : 0;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * env-flags — shared parsing for default-ON boolean environment flags.
3
+ *
4
+ * Extracted from `index.ts` (BAPI-641) so normal server startup and the
5
+ * `doctor` diagnostic share ONE implementation of the default-on semantics.
6
+ * A default-on flag is enabled unless explicitly set to a recognized off-token,
7
+ * so an unknown value fails OPEN (enabled) rather than silently disabling a
8
+ * feature.
9
+ */
10
+ /** The recognized, case-insensitive off-tokens for a default-on flag. */
11
+ const OFF_TOKENS = new Set([
12
+ "false",
13
+ "0",
14
+ "no",
15
+ "off",
16
+ "disabled",
17
+ ]);
18
+ /**
19
+ * Parse a default-ON boolean env flag. `undefined` / blank → true; the
20
+ * normalized off-tokens (`false`, `0`, `no`, `off`, `disabled`) → false; any
21
+ * other value → true (preserving default-on / fail-open behavior). Matching is
22
+ * case-insensitive and trims surrounding whitespace.
23
+ */
24
+ export function parseDefaultOnEnvFlag(value) {
25
+ if (value === undefined)
26
+ return true;
27
+ const normalized = value.trim().toLowerCase();
28
+ if (normalized === "")
29
+ return true;
30
+ return !OFF_TOKENS.has(normalized);
31
+ }